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

Message bubbles: Create new text message cells for bubbles
This commit is contained in:
SBiOSoftWhare
2022-02-09 17:29:11 +01:00
committed by GitHub
53 changed files with 1719 additions and 459 deletions
@@ -46,6 +46,11 @@
*/
NSAttributedString *attributedTextMessage;
/**
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;
+57 -23
View File
@@ -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;
}
+55
View File
@@ -2813,6 +2813,61 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
}
}
}
else if (roomBubbleCellData.getFirstBubbleComponentWithDisplay.event.isEmote)
{
if (bubbleData.isIncoming)
{
if (bubbleData.isPaginationFirstBubble)
{
if (bubbleData.shouldHideSenderName)
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName : RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName;
}
else
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle : RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle;
}
}
else if (bubbleData.shouldHideSenderInformation)
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo;
}
else if (bubbleData.shouldHideSenderName)
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName : RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName;
}
else
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncrypted : RoomTimelineCellIdentifierIncomingEmote;
}
}
else
{
if (bubbleData.isPaginationFirstBubble)
{
if (bubbleData.shouldHideSenderName)
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName : RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName;
}
else
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle : RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle;
}
}
else if (bubbleData.shouldHideSenderInformation)
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo;
}
else if (bubbleData.shouldHideSenderName)
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName : RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName;
}
else
{
cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncrypted : RoomTimelineCellIdentifierOutgoingEmote;
}
}
}
else if (bubbleData.isIncoming)
{
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
}
@@ -0,0 +1,23 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// `RoomCellURLPreviewDisplayable` is a protocol indicating that a cell support displaying a URL preview.
@objc protocol RoomCellURLPreviewDisplayable {
func addURLPreviewView(_ urlPreviewView: UIView)
func removeURLPreviewView()
}
@@ -239,6 +239,16 @@ extern NSString *const kMXKRoomBubbleCellUrlItemInteraction;
*/
@property (nonatomic) WKWebView *attachmentWebView;
/**
Indicate true if the cell needs vertical space in the text to position UI components.
*/
@property (nonatomic, readonly) BOOL isTextViewNeedsPositioningVerticalSpace;
/**
Use bubbleData.attributedTextMessage or bubbleData.attributedTextMessageWithoutPositioningSpace according to isTextViewNeedsPositioningVerticalSpace value.
*/
@property (nonatomic, readonly) NSAttributedString *suitableAttributedTextMessage;
/**
Called during the designated initializer of the UITableViewCell class to set the default
properties values.
@@ -30,6 +30,8 @@
#import "MXKMessageTextView.h"
#import "UITextView+MatrixKit.h"
#import "GeneratedInterface-Swift.h"
#pragma mark - Constant definitions
NSString *const kMXKRoomBubbleCellTapOnMessageTextView = @"kMXKRoomBubbleCellTapOnMessageTextView";
NSString *const kMXKRoomBubbleCellTapOnSenderNameLabel = @"kMXKRoomBubbleCellTapOnSenderNameLabel";
@@ -122,6 +124,7 @@ static BOOL _disableLongPressGestureOnEvent;
_allTextHighlighted = NO;
_isAutoAnimatedGif = NO;
_tmpSubviews = [NSMutableArray array];
_isTextViewNeedsPositioningVerticalSpace = YES;
}
- (void)awakeFromNib
@@ -366,18 +369,23 @@ static BOOL _disableLongPressGestureOnEvent;
{
if (_allTextHighlighted)
{
NSMutableAttributedString *highlightedString = [[NSMutableAttributedString alloc] initWithAttributedString:bubbleData.attributedTextMessage];
NSMutableAttributedString *highlightedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.suitableAttributedTextMessage];
UIColor *color = self.tintColor ? self.tintColor : [UIColor lightGrayColor];
[highlightedString addAttribute:NSBackgroundColorAttributeName value:color range:NSMakeRange(0, highlightedString.length)];
self.messageTextView.attributedText = highlightedString;
}
else
{
self.messageTextView.attributedText = bubbleData.attributedTextMessage;
self.messageTextView.attributedText = self.suitableAttributedTextMessage;
}
}
}
- (NSAttributedString *)suitableAttributedTextMessage
{
return self.isTextViewNeedsPositioningVerticalSpace ? bubbleData.attributedTextMessage : bubbleData.attributedTextMessageWithoutPositioningSpace;
}
- (void)highlightTextMessageForEvent:(NSString*)eventId
{
if (self.messageTextView)
@@ -389,7 +397,7 @@ static BOOL _disableLongPressGestureOnEvent;
else
{
// Restore original string
self.messageTextView.attributedText = bubbleData.attributedTextMessage;
self.messageTextView.attributedText = self.suitableAttributedTextMessage;
}
}
}
@@ -559,14 +567,14 @@ static BOOL _disableLongPressGestureOnEvent;
// Underline attached file name
if (self.isBubbleDataContainsFileAttachment)
{
NSMutableAttributedString *updatedText = [[NSMutableAttributedString alloc] initWithAttributedString:bubbleData.attributedTextMessage];
NSMutableAttributedString *updatedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.suitableAttributedTextMessage];
[updatedText addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, updatedText.length)];
newText = updatedText;
}
else
{
newText = bubbleData.attributedTextMessage;
newText = self.suitableAttributedTextMessage;
}
// update the text only if it is required
@@ -927,8 +935,24 @@ static BOOL _disableLongPressGestureOnEvent;
}
else if (cell.messageTextView)
{
CGFloat maxTextViewWidth;
RoomTimelineConfiguration *timelineConfiguration = [RoomTimelineConfiguration shared];
id<RoomCellLayoutUpdating> cellLayoutUpdater = timelineConfiguration.currentStyle.cellLayoutUpdater;
// Handle updated text view layout if needed
if (cellLayoutUpdater)
{
maxTextViewWidth = [cellLayoutUpdater maximumTextViewWidthFor:cell cellData:cellData maximumCellWidth:maxWidth];
}
else
{
maxTextViewWidth = maxWidth - (cell.msgTextViewLeadingConstraint.constant + cell.msgTextViewTrailingConstraint.constant);
}
// Update maximum width available for the textview
bubbleData.maxTextViewWidth = maxWidth - (cell.msgTextViewLeadingConstraint.constant + cell.msgTextViewTrailingConstraint.constant);
bubbleData.maxTextViewWidth = maxTextViewWidth;
// Retrieve the suggested height of the message content
rowHeight = bubbleData.contentSize.height;
@@ -950,101 +974,33 @@ static BOOL _disableLongPressGestureOnEvent;
{
[super prepareForReuse];
bubbleData = nil;
delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.readReceiptsAlignment = ReadReceiptAlignmentLeft;
_allTextHighlighted = NO;
_isAutoAnimatedGif = NO;
[self removeHTMLBlockquoteSideBorderViews];
[self removeTemporarySubviews];
[self cleanAttachmentView];
[self clearBubbleInfoContainer];
[self clearBubbleOverlayContainer];
[self resetConstraintsConstantToDefault];
[self clearAttachmentWebView];
[self didEndDisplay];
}
- (void)didEndDisplay
{
bubbleData = nil;
for (UIView *sideBorder in htmlBlockquoteSideBorderViews)
{
[sideBorder removeFromSuperview];
}
[htmlBlockquoteSideBorderViews removeAllObjects];
htmlBlockquoteSideBorderViews = nil;
if (_attachmentWebView)
{
[_attachmentWebView removeFromSuperview];
_attachmentWebView.navigationDelegate = nil;
_attachmentWebView = nil;
}
[self removeReadMarkerView];
[self cleanProgressView];
if (_readMarkerView)
{
[_readMarkerView removeFromSuperview];
_readMarkerView = nil;
_readMarkerViewTopConstraint = nil;
_readMarkerViewLeadingConstraint = nil;
_readMarkerViewTrailingConstraint = nil;
_readMarkerViewHeightConstraint = nil;
}
if (self.attachmentView)
{
// Remove all gesture recognizer
while (self.attachmentView.gestureRecognizers.count)
{
[self.attachmentView removeGestureRecognizer:self.attachmentView.gestureRecognizers[0]];
}
// Prevent the cell from displaying again the image in case of reuse.
self.attachmentView.image = nil;
}
// Remove potential dateTime (or unsent) label(s)
if (self.bubbleInfoContainer && self.bubbleInfoContainer.subviews.count > 0)
{
NSArray* subviews = self.bubbleInfoContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
}
self.bubbleInfoContainer.hidden = YES;
// Remove temporary subviews
for (UIView *view in self.tmpSubviews)
{
[view removeFromSuperview];
}
[self.tmpSubviews removeAllObjects];
// Remove potential overlay subviews
if (self.bubbleOverlayContainer)
{
NSArray* subviews = self.bubbleOverlayContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
self.bubbleOverlayContainer.hidden = YES;
}
if (self.progressView)
{
[self stopProgressUI];
// Remove long tap gesture on the progressView
while (self.progressView.gestureRecognizers.count)
{
[self.progressView removeGestureRecognizer:self.progressView.gestureRecognizers[0]];
}
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
delegate = nil;
self.readReceiptsAlignment = ReadReceiptAlignmentLeft;
_allTextHighlighted = NO;
_isAutoAnimatedGif = NO;
[self resetConstraintsConstantToDefault];
// TODO: Stop gif animation
}
- (BOOL)shouldInteractWithURL:(NSURL *)URL urlItemInteraction:(UITextItemInteraction)urlItemInteraction associatedEvent:(MXEvent*)associatedEvent
@@ -1135,6 +1091,111 @@ static BOOL _disableLongPressGestureOnEvent;
[self.tmpSubviews addObject:subview];
}
#pragma mark - Cleaning
- (void)removeHTMLBlockquoteSideBorderViews
{
for (UIView *sideBorder in htmlBlockquoteSideBorderViews)
{
[sideBorder removeFromSuperview];
}
[htmlBlockquoteSideBorderViews removeAllObjects];
htmlBlockquoteSideBorderViews = nil;
}
- (void)removeReadMarkerView
{
if (_readMarkerView)
{
[_readMarkerView removeFromSuperview];
_readMarkerView = nil;
_readMarkerViewTopConstraint = nil;
_readMarkerViewLeadingConstraint = nil;
_readMarkerViewTrailingConstraint = nil;
_readMarkerViewHeightConstraint = nil;
}
}
- (void)removeTemporarySubviews
{
// Remove temporary subviews
for (UIView *view in self.tmpSubviews)
{
[view removeFromSuperview];
}
[self.tmpSubviews removeAllObjects];
}
- (void)cleanAttachmentView
{
if (self.attachmentView)
{
// Remove all gesture recognizer
while (self.attachmentView.gestureRecognizers.count)
{
[self.attachmentView removeGestureRecognizer:self.attachmentView.gestureRecognizers[0]];
}
// Prevent the cell from displaying again the image in case of reuse.
self.attachmentView.image = nil;
}
}
- (void)clearBubbleInfoContainer
{
// Remove potential dateTime (or unsent) label(s)
if (self.bubbleInfoContainer && self.bubbleInfoContainer.subviews.count > 0)
{
NSArray* subviews = self.bubbleInfoContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
}
self.bubbleInfoContainer.hidden = YES;
}
- (void)clearBubbleOverlayContainer
{
// Remove potential overlay subviews
if (self.bubbleOverlayContainer)
{
NSArray* subviews = self.bubbleOverlayContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
self.bubbleOverlayContainer.hidden = YES;
}
}
- (void)cleanProgressView
{
if (self.progressView)
{
[self stopProgressUI];
// Remove long tap gesture on the progressView
while (self.progressView.gestureRecognizers.count)
{
[self.progressView removeGestureRecognizer:self.progressView.gestureRecognizers[0]];
}
}
}
- (void)clearAttachmentWebView
{
if (_attachmentWebView)
{
[_attachmentWebView removeFromSuperview];
_attachmentWebView.navigationDelegate = nil;
_attachmentWebView = nil;
}
}
#pragma mark - Attachment progress handling
- (void)updateProgressUI:(NSDictionary*)statisticsDict
@@ -47,6 +47,34 @@ typedef NS_ENUM(NSUInteger, RoomTimelineCellIdentifier) {
RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName,
RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName,
// - Emote
// -- Incoming
// --- Clear
RoomTimelineCellIdentifierIncomingEmote,
RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo,
RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle,
RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName,
RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName,
// --- Encrypted
RoomTimelineCellIdentifierIncomingEmoteEncrypted,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName,
// -- Outgoing
// --- Clear
RoomTimelineCellIdentifierOutgoingEmote,
RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo,
RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle,
RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName,
RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName,
// --- Encrypted
RoomTimelineCellIdentifierOutgoingEmoteEncrypted,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName,
// - Attachment
// -- Incoming
// --- Clear
@@ -15,6 +15,7 @@
*/
import UIKit
import MatrixSDK
@objc protocol SizableBaseBubbleCellType: BaseBubbleCellType {
static func sizingViewHeightHashValue(from bubbleCellData: MXKRoomBubbleCellData) -> Int
@@ -32,6 +33,8 @@ class SizableBaseBubbleCell: BaseBubbleCell, SizableBaseBubbleCellType {
private static let reactionsViewSizer = BubbleReactionsViewSizer()
private static let reactionsViewModelBuilder = BubbleReactionsViewModelBuilder()
private static let urlPreviewViewSizer = URLPreviewViewSizer()
private class var sizingView: SizableBaseBubbleCell {
let sizingView: SizableBaseBubbleCell
@@ -135,7 +138,22 @@ class SizableBaseBubbleCell: BaseBubbleCell, SizableBaseBubbleCellType {
if sizingView is BubbleCellThreadSummaryDisplayable,
let roomBubbleCellData = cellData as? RoomBubbleCellData,
roomBubbleCellData.hasThreadRoot {
let bottomMargin = sizingView.bubbleCellContentView?.threadSummaryContentViewBottomConstraint.constant ?? 0
height += RoomBubbleCellLayout.threadSummaryViewHeight
height += bottomMargin
}
// Add URL preview view height if needed
if sizingView is RoomCellURLPreviewDisplayable,
let roomBubbleCellData = cellData as? RoomBubbleCellData, let firstBubbleComponent =
roomBubbleCellData.getFirstBubbleComponentWithDisplay(), firstBubbleComponent.showURLPreview, let urlPreviewData = firstBubbleComponent.urlPreviewData as? URLPreviewData {
let urlPreviewMaxWidth = sizingView.bubbleCellContentView?.urlPreviewContentView.frame.width ?? roomBubbleCellData.maxTextViewWidth
let urlPreviewHeight = self.urlPreviewViewSizer.height(for: urlPreviewData, fittingWidth: urlPreviewMaxWidth)
height+=urlPreviewHeight
}
return height
@@ -0,0 +1,29 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// Bubble style room cell layout constants
@objcMembers
final class BubbleRoomCellLayoutConstants: NSObject {
static let outgoingBubbleBackgroundMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 80, bottom: 0, right: 34)
static let incomingBubbleBackgroundMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 80)
static let threadSummaryViewMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0)
}
@@ -50,40 +50,16 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
func updateLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) {
if let messageBubbleBackgroundView = cell.messageBubbleBackgroundView {
if self.canUseBubbleBackground(forCell: cell, withCellData: cellData) {
messageBubbleBackgroundView.isHidden = false
self.updateMessageBubbleBackgroundView(messageBubbleBackgroundView, withCell: cell, andCellData: cellData)
} else {
messageBubbleBackgroundView.isHidden = true
}
}
}
func updateLayout(forOutgoingTextMessageCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) {
if let messageBubbleBackgroundView = cell.messageBubbleBackgroundView {
if self.canUseBubbleBackground(forCell: cell, withCellData: cellData) {
messageBubbleBackgroundView.isHidden = false
self.updateMessageBubbleBackgroundView(messageBubbleBackgroundView, withCell: cell, andCellData: cellData)
} else {
messageBubbleBackgroundView.isHidden = true
}
}
}
func setupLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell) {
self.setupIncomingMessageTextViewMargins(for: cell)
self.addBubbleBackgroundViewToCell(cell, backgroundColor: self.incomingColor)
cell.setNeedsUpdateConstraints()
}
@@ -94,8 +70,6 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
// Hide avatar view
cell.pictureView?.isHidden = true
self.addBubbleBackgroundViewToCell(cell, backgroundColor: self.outgoingColor)
cell.setNeedsUpdateConstraints()
}
@@ -123,6 +97,49 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
}
}
func maximumTextViewWidth(for cell: MXKRoomBubbleTableViewCell, cellData: MXKCellData, maximumCellWidth: CGFloat) -> CGFloat {
guard cell.messageTextView != nil else {
return 0
}
let maxTextViewWidth: CGFloat
let textViewleftMargin: CGFloat
let textViewRightMargin: CGFloat
if let roomBubbleCellData = cellData as? RoomBubbleCellData, cell is MXKRoomIncomingTextMsgBubbleCell || cell is MXKRoomOutgoingTextMsgBubbleCell {
if roomBubbleCellData.isIncoming {
let textViewInsets = self.getIncomingMessageTextViewInsets(from: cell)
textViewleftMargin = cell.msgTextViewLeadingConstraint.constant + textViewInsets.left
// Right inset is in fact margin in this case
textViewRightMargin = textViewInsets.right
} else {
let textViewMargins = self.getOutgoingMessageTextViewMargins(from: cell)
textViewleftMargin = textViewMargins.left
textViewRightMargin = textViewMargins.right
}
} else {
textViewleftMargin = cell.msgTextViewLeadingConstraint.constant
textViewRightMargin = cell.msgTextViewTrailingConstraint.constant
}
maxTextViewWidth = maximumCellWidth - (textViewleftMargin + textViewRightMargin)
guard maxTextViewWidth >= 0 else {
return 0
}
guard maxTextViewWidth <= maximumCellWidth else {
return maxTextViewWidth
}
return maxTextViewWidth
}
// MARK: Themable
func update(theme: Theme) {
@@ -131,166 +148,21 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
// MARK: - Private
// MARK: Bubble background view
private func createBubbleBackgroundView(with backgroundColor: UIColor) -> RoomMessageBubbleBackgroundView {
let bubbleBackgroundView = RoomMessageBubbleBackgroundView()
bubbleBackgroundView.backgroundColor = backgroundColor
return bubbleBackgroundView
}
private func addBubbleBackgroundViewToCell(_ bubbleCell: MXKRoomBubbleTableViewCell, backgroundColor: UIColor) {
guard let messageTextView = bubbleCell.messageTextView else {
return
}
let topMargin: CGFloat = 0.0
let leftMargin: CGFloat = 5.0
let rightMargin: CGFloat = 45.0 // Add extra space for timestamp
let bubbleBackgroundView = self.createBubbleBackgroundView(with: backgroundColor)
bubbleCell.contentView.insertSubview(bubbleBackgroundView, at: 0)
let topAnchor = messageTextView.topAnchor
let leadingAnchor = messageTextView.leadingAnchor
let trailingAnchor = messageTextView.trailingAnchor
bubbleBackgroundView.updateHeight(messageTextView.frame.height)
NSLayoutConstraint.activate([
bubbleBackgroundView.topAnchor.constraint(equalTo: topAnchor, constant: topMargin),
bubbleBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -leftMargin),
bubbleBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: rightMargin)
])
}
private func canUseBubbleBackground(forCell cell: MXKRoomBubbleTableViewCell, withCellData cellData: MXKRoomBubbleCellData) -> Bool {
guard let firstComponent = cellData.getFirstBubbleComponentWithDisplay(), let firstEvent = firstComponent.event else {
return false
}
switch firstEvent.eventType {
case .roomMessage:
if let messageType = firstEvent.messageType {
switch messageType {
case .text, .file:
return true
case .emote:
// Explicitely disable bubble for emotes
return false
default:
break
}
}
default:
break
}
return false
}
private func getTextMessageHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? {
guard let roomBubbleCellData = cellData as? RoomBubbleCellData,
let lastBubbleComponent = cellData.getLastBubbleComponentWithDisplay(),
let firstComponent = roomBubbleCellData.getFirstBubbleComponentWithDisplay() else {
return nil
}
let bubbleHeight: CGFloat
let lastEventId = lastBubbleComponent.event.eventId
let lastMessageBottomPosition = cell.bottomPosition(ofEvent: lastEventId)
let firstEventId = firstComponent.event.eventId
let firstMessageTopPosition = cell.topPosition(ofEvent: firstEventId)
let additionalContentHeight = roomBubbleCellData.additionalContentHeight
bubbleHeight = lastMessageBottomPosition - firstMessageTopPosition - additionalContentHeight
guard bubbleHeight >= 0 else {
return nil
}
return bubbleHeight
}
// TODO: Improve text message height calculation
// This method is closer to final result but lack of stability because of extra vertical space not handled here.
// private func getTextMessageHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? {
//
// guard let roomBubbleCellData = cellData as? RoomBubbleCellData,
// let firstComponent = roomBubbleCellData.getFirstBubbleComponentWithDisplay() else {
// return nil
// }
//
// let bubbleHeight: CGFloat
//
// let componentIndex = cellData.bubbleComponentIndex(forEventId: firstComponent.event.eventId)
//
// let componentFrame = cell.componentFrameInContentView(for: componentIndex)
//
// bubbleHeight = componentFrame.height
//
// guard bubbleHeight >= 0 else {
// return nil
// }
//
// return bubbleHeight
// }
private func getMessageBubbleBackgroundHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? {
var finalBubbleHeight: CGFloat?
let extraMargin: CGFloat = 4.0
if let bubbleHeight = self.getTextMessageHeight(for: cell, andCellData: cellData) {
finalBubbleHeight = bubbleHeight + extraMargin
} else if let messageTextViewHeight = cell.messageTextView?.frame.height {
finalBubbleHeight = messageTextViewHeight + extraMargin
}
return finalBubbleHeight
}
@discardableResult
private func updateMessageBubbleBackgroundView(_ roomMessageBubbleBackgroundView: RoomMessageBubbleBackgroundView, withCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> Bool {
if let bubbleHeight = self.getMessageBubbleBackgroundHeight(for: cell, andCellData: cellData) {
return roomMessageBubbleBackgroundView.updateHeight(bubbleHeight)
} else {
return false
}
}
// MARK: Text message
private func getIncomingMessageTextViewInsets(from bubbleCell: MXKRoomBubbleTableViewCell) -> UIEdgeInsets {
let messageViewMarginTop: CGFloat
let messageViewMarginBottom: CGFloat = -2.0
let messageViewMarginLeft: CGFloat = 3.0
let messageViewMarginRight: CGFloat = 80
if bubbleCell.userNameLabel != nil {
messageViewMarginTop = 10.0
} else {
messageViewMarginTop = 0.0
}
let bubbleBgRightMargin: CGFloat = 45
let messageViewMarginTop: CGFloat = 0
let messageViewMarginBottom: CGFloat = -0
let messageViewMarginLeft: CGFloat = 0
let messageViewMarginRight: CGFloat = 80 + bubbleBgRightMargin
let messageViewInsets = UIEdgeInsets(top: messageViewMarginTop, left: messageViewMarginLeft, bottom: messageViewMarginBottom, right: messageViewMarginRight)
return messageViewInsets
}
// MARK: Text message
private func setupIncomingMessageTextViewMargins(for cell: MXKRoomBubbleTableViewCell) {
guard cell.messageTextView != nil else {
@@ -302,7 +174,23 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
cell.msgTextViewBottomConstraint.constant += messageViewInsets.bottom
cell.msgTextViewTopConstraint.constant += messageViewInsets.top
cell.msgTextViewLeadingConstraint.constant += messageViewInsets.left
cell.msgTextViewTrailingConstraint.constant += messageViewInsets.right
// Right inset is in fact margin in this case
cell.msgTextViewTrailingConstraint.constant = messageViewInsets.right
}
private func getOutgoingMessageTextViewMargins(from bubbleCell: MXKRoomBubbleTableViewCell) -> UIEdgeInsets {
let innerContentLeftMargin: CGFloat = 57
let messageViewMarginTop: CGFloat = 0
let messageViewMarginBottom: CGFloat = 0
let messageViewMarginLeft: CGFloat = 80.0 + innerContentLeftMargin
let messageViewMarginRight: CGFloat = 78.0
let messageViewInsets = UIEdgeInsets(top: messageViewMarginTop, left: messageViewMarginLeft, bottom: messageViewMarginBottom, right: messageViewMarginRight)
return messageViewInsets
}
private func setupOutgoingMessageTextViewMargins(for cell: MXKRoomBubbleTableViewCell) {
@@ -313,16 +201,14 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
let contentView = cell.contentView
let leftMargin: CGFloat = 80.0
let rightMargin: CGFloat = 78.0
let bottomMargin: CGFloat = -2.0
let messageViewMargins = self.getOutgoingMessageTextViewMargins(from: cell)
cell.msgTextViewLeadingConstraint.isActive = false
cell.msgTextViewTrailingConstraint.isActive = false
let leftConstraint = messageTextView.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: leftMargin)
let leftConstraint = messageTextView.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: messageViewMargins.left)
let rightConstraint = messageTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -rightMargin)
let rightConstraint = messageTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -messageViewMargins.right)
NSLayoutConstraint.activate([
leftConstraint,
@@ -332,9 +218,11 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
cell.msgTextViewLeadingConstraint = leftConstraint
cell.msgTextViewTrailingConstraint = rightConstraint
cell.msgTextViewBottomConstraint.constant += bottomMargin
cell.msgTextViewBottomConstraint.constant += messageViewMargins.bottom
}
// MARK: File attachment
private func setupOutgoingFileAttachViewMargins(for cell: MXKRoomBubbleTableViewCell) {
guard let attachmentView = cell.attachmentView else {
@@ -29,24 +29,19 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
override func addTimestampLabel(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) {
if let timestampDisplayable = cell as? TimestampDisplayable, let timestampLabel = self.createTimestampLabel(for: cellData) {
guard let timestampLabel = self.createTimestampLabel(for: cellData) else {
super.addTimestampLabel(toCell: cell, cellData: cellData)
return
}
if let timestampDisplayable = cell as? TimestampDisplayable {
timestampDisplayable.addTimestampView(timestampLabel)
} else if let bubbleBackgroundView = cell.messageBubbleBackgroundView, bubbleBackgroundView.isHidden == false, let timestampLabel = self.createTimestampLabel(for: cellData) {
// If cell contains a bubble background, add the timestamp inside of it
self.addTimestampLabel(timestampLabel,
to: cell,
on: bubbleBackgroundView,
constrainingView: bubbleBackgroundView)
} else if cellData.isAttachmentWithThumbnail {
if cellData.attachment?.type == .sticker,
let attachmentView = cell.attachmentView,
let timestampLabel = self.createTimestampLabel(for: cellData) {
let attachmentView = cell.attachmentView {
// Prevent overlap with send status icon
let bottomMargin: CGFloat = 20.0
@@ -59,9 +54,11 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
rightMargin: rightMargin,
bottomMargin: bottomMargin)
} else if let attachmentView = cell.attachmentView, let timestampLabel = self.createTimestampLabel(for: cellData, textColor: self.theme.baseIconPrimaryColor) {
} else if let attachmentView = cell.attachmentView {
// For media with thumbnail cells, add timestamp inside thumbnail
timestampLabel.textColor = self.theme.baseIconPrimaryColor
self.addTimestampLabel(timestampLabel,
to: cell,
on: cell.contentView,
@@ -70,7 +67,7 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
} else {
super.addTimestampLabel(toCell: cell, cellData: cellData)
}
} else if let voiceMessageCell = cell as? VoiceMessageBubbleCell, let playbackView = voiceMessageCell.playbackController?.playbackView, let timestampLabel = self.createTimestampLabel(for: cellData) {
} else if let voiceMessageCell = cell as? VoiceMessageBubbleCell, let playbackView = voiceMessageCell.playbackController?.playbackView {
// Add timestamp on cell inherting from VoiceMessageBubbleCell
@@ -79,7 +76,7 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
on: cell.contentView,
constrainingView: playbackView)
} else if let fileWithoutThumbnailCell = cell as? FileWithoutThumbnailBaseBubbleCell, let fileAttachementView = fileWithoutThumbnailCell.fileAttachementView, let timestampLabel = self.createTimestampLabel(for: cellData) {
} else if let fileWithoutThumbnailCell = cell as? FileWithoutThumbnailBaseBubbleCell, let fileAttachementView = fileWithoutThumbnailCell.fileAttachementView {
// Add timestamp on cell inherting from VoiceMessageBubbleCell
@@ -96,13 +93,13 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
override func addReactionView(_ reactionsView: BubbleReactionsView,
toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData, contentViewPositionY: CGFloat, upperDecorationView: UIView?) {
cell.addTemporarySubview(reactionsView)
if let reactionsDisplayable = cell as? BubbleCellReactionsDisplayable {
reactionsDisplayable.addReactionsView(reactionsView)
return
}
cell.addTemporarySubview(reactionsView)
reactionsView.translatesAutoresizingMaskIntoConstraints = false
let cellContentView = cell.contentView
@@ -170,44 +167,49 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
cellData: RoomBubbleCellData,
contentViewPositionY: CGFloat) {
cell.addTemporarySubview(urlPreviewView)
let cellContentView = cell.contentView
urlPreviewView.translatesAutoresizingMaskIntoConstraints = false
urlPreviewView.availableWidth = cellData.maxTextViewWidth
cellContentView.addSubview(urlPreviewView)
let leadingOrTrailingConstraint: NSLayoutConstraint
// Incoming message
if cellData.isIncoming {
if let urlPreviewDisplayable = cell as? RoomCellURLPreviewDisplayable {
urlPreviewView.translatesAutoresizingMaskIntoConstraints = false
urlPreviewDisplayable.addURLPreviewView(urlPreviewView)
} else {
cell.addTemporarySubview(urlPreviewView)
let cellContentView = cell.contentView
urlPreviewView.translatesAutoresizingMaskIntoConstraints = false
urlPreviewView.availableWidth = cellData.maxTextViewWidth
cellContentView.addSubview(urlPreviewView)
let leadingOrTrailingConstraint: NSLayoutConstraint
// Incoming message
if cellData.isIncoming {
var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin
if cellData.containsBubbleComponentWithEncryptionBadge {
leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin
var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin
if cellData.containsBubbleComponentWithEncryptionBadge {
leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin
}
leftMargin-=5.0
leadingOrTrailingConstraint = urlPreviewView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin)
} else {
// Outgoing message
// TODO: Use constants
let rightMargin: CGFloat = 34.0
leadingOrTrailingConstraint = urlPreviewView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -rightMargin)
}
leftMargin-=5.0
let topMargin = contentViewPositionY + RoomBubbleCellLayout.urlPreviewViewTopMargin + RoomBubbleCellLayout.reactionsViewTopMargin
leadingOrTrailingConstraint = urlPreviewView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin)
} else {
// Outgoing message
// TODO: Use constants
let rightMargin: CGFloat = 34.0
leadingOrTrailingConstraint = urlPreviewView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -rightMargin)
// Set the preview view's origin
NSLayoutConstraint.activate([
leadingOrTrailingConstraint,
urlPreviewView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: topMargin)
])
}
let topMargin = contentViewPositionY + RoomBubbleCellLayout.urlPreviewViewTopMargin + RoomBubbleCellLayout.reactionsViewTopMargin
// Set the preview view's origin
NSLayoutConstraint.activate([
leadingOrTrailingConstraint,
urlPreviewView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: topMargin)
])
}
override func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView,
@@ -216,11 +218,11 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
contentViewPositionY: CGFloat,
upperDecorationView: UIView?) {
cell.addTemporarySubview(threadSummaryView)
if let threadSummaryDisplayable = cell as? BubbleCellThreadSummaryDisplayable {
threadSummaryDisplayable.addThreadSummaryView(threadSummaryView)
} else {
cell.addTemporarySubview(threadSummaryView)
threadSummaryView.translatesAutoresizingMaskIntoConstraints = false
let cellContentView = cell.contentView
@@ -64,6 +64,28 @@
[self registerFileWithoutThumbnailCellsForTableView:tableView];
}
- (void)registerIncomingTextMessageCellsForTableView:(UITableView*)tableView
{
// Also register legacy cells for notice and emotes
[super registerIncomingTextMessageCellsForTableView:tableView];
[tableView registerClass:TextMessageIncomingBubbleCell.class forCellReuseIdentifier:TextMessageIncomingBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithoutSenderNameBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithoutSenderNameBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier];
}
- (void)registerOutgoingTextMessageCellsForTableView:(UITableView*)tableView
{
// Also register legacy cells for notice and emotes
[super registerOutgoingTextMessageCellsForTableView:tableView];
[tableView registerClass:TextMessageOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:TextMessageOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:TextMessageOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier];
}
- (void)registerVoiceMessageCellsForTableView:(UITableView*)tableView
{
// Incoming
@@ -112,22 +134,77 @@
#pragma mark - Mapping
- (NSDictionary<NSNumber*, Class>*)incomingTextMessageCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierIncomingTextMessage) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierIncomingTextMessageEncrypted) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)outgoingTextMessageCellsMapping
{
// Hide sender info and avatar for bubble outgoing messages
return @{
// Clear
@(RoomTimelineCellIdentifierOutgoingTextMessage) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName) : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessage) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierOutgoingTextMessageEncrypted) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncrypted) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)incomingEmoteCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierIncomingEmote) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierIncomingEmoteEncrypted) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)outgoingEmoteCellsMapping
{
// Hide sender info and avatar for bubble outgoing messages
return @{
// Clear
@(RoomTimelineCellIdentifierOutgoingEmote) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierOutgoingEmoteEncrypted) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleBubbleCell.class
};
}
@@ -0,0 +1,54 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol BubbleIncomingRoomCellProtocol: BubbleRoomCellProtocol {
}
extension BubbleIncomingRoomCellProtocol {
// MARK: - Public
func setupBubbleDecorations() {
self.bubbleCellContentView?.decorationViewsAlignment = .left
self.setupDecorationConstraints()
}
// MARK: - Private
private func setupDecorationConstraints() {
self.setupURLPreviewContentViewContraints()
self.setupReactionsContentViewContraints()
self.setupThreadSummaryViewContentViewContraints()
}
private func setupReactionsContentViewContraints() {
self.bubbleCellContentView?.reactionsContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right
}
private func setupThreadSummaryViewContentViewContraints() {
self.bubbleCellContentView?.threadSummaryContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right
self.bubbleCellContentView?.threadSummaryContentViewBottomConstraint.constant = BubbleRoomCellLayoutConstants.threadSummaryViewMargins.bottom
}
private func setupURLPreviewContentViewContraints() {
self.bubbleCellContentView?.urlPreviewContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right
}
}
@@ -0,0 +1,118 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol BubbleOutgoingRoomCellProtocol: BubbleRoomCellProtocol {
}
// MARK: - Default implementation
extension BubbleOutgoingRoomCellProtocol {
// MARK: - Public
func setupBubbleDecorations() {
self.bubbleCellContentView?.decorationViewsAlignment = .right
self.setupDecorationConstraints()
}
// MARK: - Private
private func setupDecorationConstraints() {
self.setupURLPreviewContentViewContraints()
self.setupReactionsContentViewContraints()
self.setupThreadSummaryViewContentViewContraints()
}
private func setupReactionsContentViewContraints() {
guard let bubbleCellContentView = self.bubbleCellContentView, let reactionsContentView = bubbleCellContentView.reactionsContentView, let reactionsContainerView = bubbleCellContentView.reactionsContainerView else {
return
}
// Remove leading constraint
bubbleCellContentView.reactionsContentViewLeadingConstraint.isActive = false
bubbleCellContentView.reactionsContentViewLeadingConstraint = nil
// Setup new leading constraint
let leadingConstraint = self.setupDecorationViewLeadingContraint(containerView: reactionsContainerView, contentView: reactionsContentView)
bubbleCellContentView.reactionsContentViewLeadingConstraint = leadingConstraint
// Update trailing constraint
bubbleCellContentView.reactionsContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
}
private func setupThreadSummaryViewContentViewContraints() {
guard let bubbleCellContentView = self.bubbleCellContentView, let threadSummaryContentView = bubbleCellContentView.threadSummaryContentView, let threadSummaryContainerView = bubbleCellContentView.threadSummaryContainerView else {
return
}
// Remove leading constraint
bubbleCellContentView.threadSummaryContentViewLeadingConstraint.isActive = false
bubbleCellContentView.threadSummaryContentViewLeadingConstraint = nil
// Setup new leading constraint
let leadingConstraint = self.setupDecorationViewLeadingContraint(containerView: threadSummaryContainerView, contentView: threadSummaryContentView)
bubbleCellContentView.threadSummaryContentViewLeadingConstraint = leadingConstraint
// Update trailing constraint
bubbleCellContentView.threadSummaryContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
// Update bottom constraint
bubbleCellContentView.threadSummaryContentViewBottomConstraint.constant = BubbleRoomCellLayoutConstants.threadSummaryViewMargins.bottom
}
private func setupURLPreviewContentViewContraints() {
guard let bubbleCellContentView = self.bubbleCellContentView, let contentView = bubbleCellContentView.urlPreviewContentView, let containerView = bubbleCellContentView.urlPreviewContainerView else {
return
}
// Remove leading constraint
bubbleCellContentView.urlPreviewContentViewLeadingConstraint.isActive = false
bubbleCellContentView.urlPreviewContentViewLeadingConstraint = nil
// Setup new leading constraint
let leadingConstraint = self.setupDecorationViewLeadingContraint(containerView: containerView, contentView: contentView)
bubbleCellContentView.urlPreviewContentViewLeadingConstraint = leadingConstraint
// Update trailing constraint
bubbleCellContentView.urlPreviewContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
}
private func setupDecorationViewLeadingContraint(containerView: UIView,
contentView: UIView) -> NSLayoutConstraint {
let leadingConstraint = contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left)
leadingConstraint.isActive = true
return leadingConstraint
}
}
@@ -0,0 +1,24 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol BubbleRoomCellProtocol {
var bubbleCellContentView: BubbleCellContentView? { get }
func setupBubbleDecorations()
}
@@ -22,12 +22,8 @@ class FileWithoutThumbnailBaseBubbleCell: SizableBaseBubbleCell, BubbleCellReact
override func render(_ cellData: MXKCellData!) {
super.render(cellData)
guard let data = cellData as? RoomBubbleCellData else {
return
}
self.fileAttachementView?.title = data.attributedTextMessage.string
self.fileAttachementView?.titleLabel.attributedText = self.suitableAttributedTextMessage
self.update(theme: ThemeService.shared().theme)
}
@@ -31,7 +31,7 @@ final class FileWithoutThumbnailCellContentView: UIView, NibLoadable {
// MARK: Outlets
@IBOutlet private weak var iconImageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private(set) weak var titleLabel: UILabel!
// MARK: Public
@@ -44,15 +44,6 @@ final class FileWithoutThumbnailCellContentView: UIView, NibLoadable {
}
}
var title: String? {
get {
return self.titleLabel.text
}
set {
self.titleLabel.text = newValue
}
}
// MARK: - Setup
static func instantiate() -> FileWithoutThumbnailCellContentView {
@@ -79,4 +70,3 @@ final class FileWithoutThumbnailCellContentView: UIView, NibLoadable {
self.titleLabel.textColor = theme.textPrimaryColor
}
}
@@ -16,7 +16,7 @@
import UIKit
class FileWithoutThumbnailIncomingBubbleCell: FileWithoutThumbnailBaseBubbleCell {
class FileWithoutThumbnailIncomingBubbleCell: FileWithoutThumbnailBaseBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -29,6 +29,8 @@ class FileWithoutThumbnailIncomingBubbleCell: FileWithoutThumbnailBaseBubbleCell
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -16,7 +16,7 @@
import UIKit
class FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell: FileWithoutThumbnailBaseBubbleCell {
class FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell: FileWithoutThumbnailBaseBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -30,6 +30,8 @@ class FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell: FileWithoutThumbna
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -16,7 +16,7 @@
import Foundation
class LocationIncomingBubbleCell: LocationBubbleCell {
class LocationIncomingBubbleCell: LocationBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -27,5 +27,7 @@ class LocationIncomingBubbleCell: LocationBubbleCell {
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.setupBubbleDecorations()
}
}
@@ -16,18 +16,17 @@
import Foundation
class LocationOutgoingWithoutSenderInfoBubbleCell: LocationBubbleCell {
class LocationOutgoingWithoutSenderInfoBubbleCell: LocationBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = false
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left
// TODO: Use constants
// Same as outgoing message
let rightMargin: CGFloat = 34.0
let leftMargin: CGFloat = 80.0
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
self.setupBubbleDecorations()
}
}
@@ -16,7 +16,7 @@
import Foundation
class PollIncomingBubbleCell: PollBaseBubbleCell {
class PollIncomingBubbleCell: PollBaseBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -27,7 +27,9 @@ class PollIncomingBubbleCell: PollBaseBubbleCell {
let messageLeftMargin: CGFloat = 48 + bubbleBackgroundSideMargin
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -16,7 +16,7 @@
import Foundation
class PollOutgoingWithoutSenderInfoBubbleCell: PollBaseBubbleCell {
class PollOutgoingWithoutSenderInfoBubbleCell: PollBaseBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -31,6 +31,8 @@ class PollOutgoingWithoutSenderInfoBubbleCell: PollBaseBubbleCell {
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -0,0 +1,112 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageBaseBubbleCell: SizableBaseBubbleCell, RoomCellURLPreviewDisplayable, BubbleCellReactionsDisplayable, BubbleCellThreadSummaryDisplayable, BubbleCellReadReceiptsDisplayable {
// MARK: - Properties
weak var textMessageContentView: TextMessageBubbleCellContentView?
override var messageTextView: UITextView! {
get {
return self.textMessageContentView?.textView
}
set { }
}
// MARK: - Overrides
override func setupViews() {
super.setupViews()
bubbleCellContentView?.backgroundColor = .clear
guard let contentView = bubbleCellContentView?.innerContentView else {
return
}
// TODO: Use constants
let messageLeftMargin: CGFloat = 48
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.bubbleCellContentView?.innerContentViewBottomContraint.constant = 5.0
self.bubbleCellContentView?.innerContentViewTrailingConstraint.constant = 34.0
let textMessageContentView = TextMessageBubbleCellContentView.instantiate()
contentView.vc_addSubViewMatchingParent(textMessageContentView)
self.textMessageContentView = textMessageContentView
}
override func update(theme: Theme) {
super.update(theme: theme)
if let messageTextView = self.messageTextView {
messageTextView.tintColor = theme.tintColor
}
// self.setupDebug()
}
// MARK: - Private
private func setupDebug() {
self.bubbleCellContentView?.innerContentView.backgroundColor = .yellow
self.bubbleCellContentView?.layer.borderWidth = 1.0
self.bubbleCellContentView?.layer.borderColor = UIColor.red.cgColor
self.textMessageContentView?.layer.borderColor = UIColor.blue.cgColor
self.textMessageContentView?.layer.borderWidth = 1.0
self.bubbleCellContentView?.readReceiptsContainerView.layer.borderColor = UIColor.yellow.cgColor
self.bubbleCellContentView?.readReceiptsContainerView.layer.borderWidth = 1.0
self.bubbleCellContentView?.reactionsContainerView.layer.borderColor = UIColor.blue.cgColor
self.bubbleCellContentView?.reactionsContainerView.layer.borderWidth = 1.0
self.bubbleCellContentView?.reactionsContentView.backgroundColor = .blue
self.bubbleCellContentView?.threadSummaryContainerView.layer.borderColor = UIColor.purple.cgColor
self.bubbleCellContentView?.threadSummaryContainerView.layer.borderWidth = 1.0
}
}
// MARK: - RoomCellTimestampDisplayable
extension TextMessageBaseBubbleCell: TimestampDisplayable {
func addTimestampView(_ timestampView: UIView) {
guard let messageBubbleBackgroundView = self.textMessageContentView?.bubbleBackgroundView else {
return
}
messageBubbleBackgroundView.removeTimestampView()
messageBubbleBackgroundView.addTimestampView(timestampView)
}
func removeTimestampView() {
guard let messageBubbleBackgroundView = self.textMessageContentView?.bubbleBackgroundView else {
return
}
messageBubbleBackgroundView.removeTimestampView()
}
}
@@ -0,0 +1,38 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
import Reusable
final class TextMessageBubbleCellContentView: UIView, NibLoadable {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private(set) weak var bubbleBackgroundView: RoomMessageBubbleBackgroundView!
@IBOutlet weak var bubbleBackgroundViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var bubbleBackgroundViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet private(set) weak var textView: UITextView!
// MARK: - Setup
static func instantiate() -> TextMessageBubbleCellContentView {
return TextMessageBubbleCellContentView.loadFromNib()
}
}
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="8T9-hj-ply" customClass="TextMessageBubbleCellContentView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="313" height="82"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wRb-1K-GDt" customClass="RoomMessageBubbleBackgroundView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="152" height="82"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="text message" translatesAutoresizingMaskIntoConstraints="NO" id="1WT-up-UFQ" customClass="MXKMessageTextView">
<rect key="frame" x="5" y="5" width="102" height="72"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="MessageTextView"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="1WT-up-UFQ" secondAttribute="bottom" constant="5" id="EMT-p9-z4L"/>
<constraint firstItem="1WT-up-UFQ" firstAttribute="leading" secondItem="wRb-1K-GDt" secondAttribute="leading" constant="5" id="GhZ-J0-v1m"/>
<constraint firstItem="1WT-up-UFQ" firstAttribute="top" secondItem="wRb-1K-GDt" secondAttribute="top" constant="5" id="OpF-jQ-D8Q"/>
<constraint firstAttribute="trailing" secondItem="1WT-up-UFQ" secondAttribute="trailing" constant="45" id="Y8T-HE-6NX"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="wRb-1K-GDt" firstAttribute="leading" secondItem="8T9-hj-ply" secondAttribute="leading" id="D7Y-mm-KEV"/>
<constraint firstAttribute="bottom" secondItem="wRb-1K-GDt" secondAttribute="bottom" id="JmB-yf-eq5"/>
<constraint firstItem="wRb-1K-GDt" firstAttribute="top" secondItem="8T9-hj-ply" secondAttribute="top" id="Jmy-ue-Qtg"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="wRb-1K-GDt" secondAttribute="trailing" constant="80" id="UwK-6h-MvN"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="bubbleBackgroundView" destination="wRb-1K-GDt" id="TWb-A8-15y"/>
<outlet property="bubbleBackgroundViewLeadingConstraint" destination="D7Y-mm-KEV" id="4le-fw-axb"/>
<outlet property="bubbleBackgroundViewTrailingConstraint" destination="UwK-6h-MvN" id="ZCA-gE-L11"/>
<outlet property="textView" destination="1WT-up-UFQ" id="Zh6-C0-kdV"/>
</connections>
<point key="canvasLocation" x="-1454.3478260869567" y="-392.41071428571428"/>
</view>
</objects>
</document>
@@ -0,0 +1,48 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingBubbleCell: TextMessageBaseBubbleCell, BubbleIncomingRoomCellProtocol {
// MARK: - Overrides
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = true
self.setupBubbleConstraints()
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
super.update(theme: theme)
self.textMessageContentView?.bubbleBackgroundView?.backgroundColor = theme.roomCellIncomingBubbleBackgroundColor
}
// MARK: - Private
private func setupBubbleConstraints() {
self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.left
let innerContentViewTrailingMargin = self.bubbleCellContentView?.innerContentViewTrailingConstraint.constant ?? 0
self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right - innerContentViewTrailingMargin
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithPaginationTitleBubbleCell: TextMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showPaginationTitle = true
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell: TextMessageIncomingWithPaginationTitleBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderName = false
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithoutSenderInfoBubbleCell: TextMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = false
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithoutSenderNameBubbleCell: TextMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderName = false
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageOutgoingWithPaginationTitleBubbleCell: TextMessageOutgoingWithoutSenderInfoBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showPaginationTitle = true
}
}
@@ -0,0 +1,24 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
class TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell: TextMessageOutgoingWithPaginationTitleBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderName = false
}
}
@@ -0,0 +1,73 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageOutgoingWithoutSenderInfoBubbleCell: TextMessageBaseBubbleCell, BubbleOutgoingRoomCellProtocol {
// MARK: - Overrides
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = false
self.setupBubbleConstraints()
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
super.update(theme: theme)
self.textMessageContentView?.bubbleBackgroundView?.backgroundColor = theme.roomCellOutgoingBubbleBackgroundColor
}
// MARK: - Private
private func setupBubbleConstraints() {
guard let containerView = self.textMessageContentView, let bubbleBackgroundView = containerView.bubbleBackgroundView else {
return
}
// Remove existing contraints
if let bubbleBackgroundViewLeadingConstraint = self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint {
bubbleBackgroundViewLeadingConstraint.isActive = false
self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint = nil
}
if let bubbleBackgroundViewTrailingConstraint = self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint {
bubbleBackgroundViewTrailingConstraint.isActive = false
self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint = nil
}
// Setup new constraints
let leadingConstraint = bubbleBackgroundView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor, constant: BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left)
let trailingConstraint = bubbleBackgroundView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0)
NSLayoutConstraint.activate([
leadingConstraint,
trailingConstraint
])
self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint = leadingConstraint
self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint = trailingConstraint
}
}
@@ -16,7 +16,7 @@
import Foundation
class VoiceMessageIncomingBubbleCell: VoiceMessageBubbleCell {
class VoiceMessageIncomingBubbleCell: VoiceMessageBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -29,7 +29,9 @@ class VoiceMessageIncomingBubbleCell: VoiceMessageBubbleCell {
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin
playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -17,6 +17,7 @@
import Foundation
class VoiceMessageIncomingWithPaginationTitleBubbleCell: VoiceMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
@@ -16,7 +16,7 @@
import Foundation
class VoiceMessageOutgoingWithoutSenderInfoBubbleCell: VoiceMessageBubbleCell {
class VoiceMessageOutgoingWithoutSenderInfoBubbleCell: VoiceMessageBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -33,6 +33,8 @@ class VoiceMessageOutgoingWithoutSenderInfoBubbleCell: VoiceMessageBubbleCell {
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -77,7 +77,11 @@ class RoomMessageBubbleBackgroundView: UIView {
// MARK: - TimestampDisplayable
extension RoomMessageBubbleBackgroundView: TimestampDisplayable {
func addTimestampView(_ timestampView: UIView) {
self.removeTimestampView()
self.addTimestampView(timestampView, rightMargin: 8.0, bottomMargin: 4.0)
self.timestampView = timestampView
}
@@ -22,6 +22,10 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Registration
- (void)registerIncomingTextMessageCellsForTableView:(UITableView*)tableView;
- (void)registerOutgoingTextMessageCellsForTableView:(UITableView*)tableView;
- (void)registerVoiceMessageCellsForTableView:(UITableView*)tableView;
- (void)registerPollCellsForTableView:(UITableView*)tableView;
@@ -30,8 +34,14 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Mapping
- (NSDictionary<NSNumber*, Class>*)incomingTextMessageCellsMapping;
- (NSDictionary<NSNumber*, Class>*)outgoingTextMessageCellsMapping;
- (NSDictionary<NSNumber*, Class>*)incomingEmoteCellsMapping;
- (NSDictionary<NSNumber*, Class>*)outgoingEmoteCellsMapping;
- (NSDictionary<NSNumber*, Class>*)outgoingAttachmentCellsMapping;
- (NSDictionary<NSNumber*, Class>*)incomingAttachmentWithoutThumbnailCellsMapping;
@@ -275,6 +275,13 @@
NSDictionary *outgoingTextMessageCellsMapping = [self outgoingTextMessageCellsMapping];
[cellClasses addEntriesFromDictionary:outgoingTextMessageCellsMapping];
// Emote
NSDictionary *incomingEmoteCellsMapping = [self incomingEmoteCellsMapping];
[cellClasses addEntriesFromDictionary:incomingEmoteCellsMapping];
NSDictionary *outgoingEmoteCellsMapping = [self outgoingEmoteCellsMapping];
[cellClasses addEntriesFromDictionary:outgoingEmoteCellsMapping];
// Attachment
NSDictionary *incomingAttachmentCellsMapping = [self incomingAttachmentCellsMapping];
@@ -360,6 +367,42 @@
};
}
- (NSDictionary<NSNumber*, Class>*)incomingEmoteCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierIncomingEmote) : RoomIncomingTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo) : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle) : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName) : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName) : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierIncomingEmoteEncrypted) : RoomIncomingEncryptedTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo) : RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle) : RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName) : RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName) : RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)outgoingEmoteCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierOutgoingEmote) : RoomOutgoingTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle) : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName) : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierOutgoingEmoteEncrypted) : RoomOutgoingEncryptedTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle) : RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)incomingAttachmentCellsMapping
{
return @{
@@ -32,8 +32,8 @@ class PlainRoomTimelineStyle: RoomTimelineStyle {
let cellProvider: RoomTimelineCellProvider
let cellDecorator: RoomTimelineCellDecorator
let cellDecorator: RoomTimelineCellDecorator
// MARK: - Setup
init(theme: Theme) {
@@ -29,4 +29,6 @@ protocol RoomCellLayoutUpdating: Themable {
func setupLayout(forOutgoingFileAttachmentCell cell: MXKRoomBubbleTableViewCell)
func updateLayout(forSelectedStickerCell cell: RoomSelectedStickerBubbleCell)
func maximumTextViewWidth(for cell: MXKRoomBubbleTableViewCell, cellData: MXKCellData, maximumCellWidth: CGFloat) -> CGFloat
}
@@ -0,0 +1,43 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the Lircense.
//
import Foundation
/// `URLPreviewViewSizer` allows to determine reactions view height for a given urlPreviewData and width.
class URLPreviewViewSizer {
// MARK: - Constants
private static let sizingView = URLPreviewView.instantiate()
// MARK: - Public
func height(for urlPreviewData: URLPreviewData, fittingWidth width: CGFloat) -> CGFloat {
let sizingView = URLPreviewViewSizer.sizingView
sizingView.frame.size.height = 1.0
sizingView.preview = urlPreviewData
sizingView.availableWidth = width
sizingView.setNeedsLayout()
sizingView.layoutIfNeeded()
let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height)
return sizingView.systemLayoutSizeFitting(fittingSize).height
}
}