mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-29 07:42:40 +00:00
Add cell layout updater implementation for bubbles.
This commit is contained in:
parent
b916968139
commit
f90f2fba0e
1 changed files with 293 additions and 0 deletions
|
@ -0,0 +1,293 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
@objcMembers
|
||||
class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdaterProtocol {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let bubbleBackgroundViewCornerRadius: CGFloat = 12.0
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var incomingColor: UIColor {
|
||||
return ThemeService.shared().theme.colors.system
|
||||
}
|
||||
|
||||
private var outgoingColor: UIColor {
|
||||
return ThemeService.shared().theme.colors.accent.withAlphaComponent(0.10)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func updateLayoutIfNeeded(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) {
|
||||
|
||||
var isMessageSenderCurrentUser: Bool = false
|
||||
|
||||
if let senderId = cellData.senderId, let currentUserId = cellData.mxSession.myUserId, senderId == currentUserId {
|
||||
isMessageSenderCurrentUser = true
|
||||
}
|
||||
|
||||
if isMessageSenderCurrentUser {
|
||||
self.updateLayout(forOutgoingTextMessageCell: cell, andCellData: cellData)
|
||||
} else {
|
||||
self.updateLayout(forIncomingTextMessageCell: cell, andCellData: cellData)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func updateLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) {
|
||||
|
||||
if let messageBubbleBackgroundView = self.getMessageBubbleBackgroundView(from: cell) {
|
||||
|
||||
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 = self.getMessageBubbleBackgroundView(from: cell) {
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func setupLayout(forOutgoingTextMessageCell cell: MXKRoomBubbleTableViewCell) {
|
||||
|
||||
self.setupOutgoingMessageTextViewMargins(for: cell)
|
||||
|
||||
// Hide avatar view
|
||||
cell.pictureView?.isHidden = true
|
||||
|
||||
self.addBubbleBackgroundViewToCell(cell, backgroundColor: self.outgoingColor)
|
||||
|
||||
cell.setNeedsUpdateConstraints()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
// MARK: Bubble background view
|
||||
|
||||
private func createBubbleBackgroundView(with backgroundColor: UIColor) -> RoomMessageBubbleBackgroundView {
|
||||
|
||||
let bubbleBackgroundView = RoomMessageBubbleBackgroundView()
|
||||
bubbleBackgroundView.backgroundColor = backgroundColor
|
||||
|
||||
return bubbleBackgroundView
|
||||
}
|
||||
|
||||
func getMessageBubbleBackgroundView(from cell: MXKRoomBubbleTableViewCell) -> RoomMessageBubbleBackgroundView? {
|
||||
|
||||
let foundView = cell.contentView.subviews.first { view in
|
||||
return view is RoomMessageBubbleBackgroundView
|
||||
}
|
||||
|
||||
return foundView as? RoomMessageBubbleBackgroundView
|
||||
}
|
||||
|
||||
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 = 5.0
|
||||
|
||||
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 messageTypeString = firstEvent.content["msgtype"] as? String {
|
||||
|
||||
let messageType = MXMessageType(identifier: messageTypeString)
|
||||
|
||||
switch messageType {
|
||||
case .text :
|
||||
return true
|
||||
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 bottomMargin: CGFloat = 4.0
|
||||
|
||||
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.additionalHeight(forEvent: lastEventId)
|
||||
|
||||
bubbleHeight = lastMessageBottomPosition - firstMessageTopPosition - additionalContentHeight + bottomMargin
|
||||
|
||||
guard bubbleHeight >= 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bubbleHeight
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func updateMessageBubbleBackgroundView(_ roomMessageBubbleBackgroundView: RoomMessageBubbleBackgroundView, withCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> Bool {
|
||||
|
||||
var finalBubbleHeight: CGFloat?
|
||||
|
||||
if let bubbleHeight = self.getTextMessageHeight(for: cell, andCellData: cellData) {
|
||||
finalBubbleHeight = bubbleHeight
|
||||
|
||||
} else if let messageTextViewHeight = cell.messageTextView?.frame.height {
|
||||
|
||||
finalBubbleHeight = messageTextViewHeight
|
||||
}
|
||||
|
||||
if let finalBubbleHeight = finalBubbleHeight {
|
||||
return roomMessageBubbleBackgroundView.updateHeight(finalBubbleHeight)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func getIncomingMessageTextViewInsets(from bubbleCell: MXKRoomBubbleTableViewCell) -> UIEdgeInsets {
|
||||
|
||||
let messageViewMarginTop: CGFloat
|
||||
let messageViewMarginBottom: CGFloat = -2.0
|
||||
let messageViewMarginLeft: CGFloat = 3.0
|
||||
let messageViewMarginRight: CGFloat = 0.0
|
||||
|
||||
if bubbleCell.userNameLabel != nil {
|
||||
messageViewMarginTop = 10.0
|
||||
} else {
|
||||
messageViewMarginTop = 0.0
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
let messageViewInsets = self.getIncomingMessageTextViewInsets(from: cell)
|
||||
|
||||
cell.msgTextViewBottomConstraint.constant += messageViewInsets.bottom
|
||||
cell.msgTextViewTopConstraint.constant += messageViewInsets.top
|
||||
cell.msgTextViewLeadingConstraint.constant += messageViewInsets.left
|
||||
cell.msgTextViewTrailingConstraint.constant += messageViewInsets.right
|
||||
}
|
||||
|
||||
private func setupOutgoingMessageTextViewMargins(for cell: MXKRoomBubbleTableViewCell) {
|
||||
|
||||
guard let messageTextView = cell.messageTextView else {
|
||||
return
|
||||
}
|
||||
|
||||
let contentView = cell.contentView
|
||||
|
||||
let leftMargin: CGFloat = 80.0
|
||||
let rightMargin: CGFloat = 38.0
|
||||
let bottomMargin: CGFloat = -2.0
|
||||
|
||||
cell.msgTextViewLeadingConstraint.isActive = false
|
||||
cell.msgTextViewTrailingConstraint.isActive = false
|
||||
|
||||
let leftConstraint = messageTextView.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: leftMargin)
|
||||
|
||||
let rightConstraint = messageTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -rightMargin)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
leftConstraint,
|
||||
rightConstraint
|
||||
])
|
||||
|
||||
cell.msgTextViewLeadingConstraint = leftConstraint
|
||||
cell.msgTextViewTrailingConstraint = rightConstraint
|
||||
|
||||
cell.msgTextViewBottomConstraint.constant += bottomMargin
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue