// // Copyright 2020-2024 New Vector Ltd. // // SPDX-License-Identifier: AGPL-3.0-only // Please see LICENSE in the repository root for full details. // import UIKit import Reusable import ReadMoreTextView class RoomInfoBasicView: UIView { private enum TopicTextViewConstants { static let font = UIFont.systemFont(ofSize: 15) static let defaultNumberOfLines = 4 static let moreLessTextPadding = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) } @IBOutlet private weak var mainStackView: UIStackView! @IBOutlet private weak var avatarContainerView: UIView! @IBOutlet private weak var avatarImageView: MXKImageView! @IBOutlet private weak var badgeImageView: UIImageView! @IBOutlet private weak var presenceIndicatorView: PresenceIndicatorView! { didSet { presenceIndicatorView.delegate = self } } @IBOutlet private weak var roomNameStackView: UIStackView! @IBOutlet private weak var roomNameLabel: UILabel! @IBOutlet private weak var roomAddressLabel: UILabel! @IBOutlet private weak var topicContainerView: UIView! @IBOutlet private weak var topicTitleLabel: UILabel! { didSet { topicTitleLabel.text = VectorL10n.roomDetailsTopic } } @IBOutlet private weak var roomTopicTextView: ReadMoreTextView! { didSet { roomTopicTextView.contentInset = .zero roomTopicTextView.textContainerInset = .zero roomTopicTextView.textContainer.lineFragmentPadding = 0 roomTopicTextView.readMoreTextPadding = TopicTextViewConstants.moreLessTextPadding roomTopicTextView.readLessTextPadding = TopicTextViewConstants.moreLessTextPadding roomTopicTextView.shouldTrim = true roomTopicTextView.maximumNumberOfLines = TopicTextViewConstants.defaultNumberOfLines roomTopicTextView.onSizeChange = { _ in self.roomTopicTextView.textAlignment = .left self.onTopicSizeChange?(self) } } } @IBOutlet private weak var securityContainerView: UIView! @IBOutlet private weak var securityTitleLabel: UILabel! @IBOutlet private weak var securityInformationLabel: UILabel! private var pillImageView: UIImageView? private var roomDisplayName: String? private var isRoomFederated: Bool = false /// Block to be invoked when topic text view changes its content size. var onTopicSizeChange: ((RoomInfoBasicView) -> Void)? /// Force to update topic text view trimming. func updateTrimmingOnTopic() { roomTopicTextView.setNeedsUpdateTrim() let currentValue = roomTopicTextView.shouldTrim roomTopicTextView.shouldTrim = !currentValue roomTopicTextView.shouldTrim = currentValue roomTopicTextView.textAlignment = .left } func configure(withViewData viewData: RoomInfoBasicViewData) { let avatarImage = AvatarGenerator.generateAvatar(forMatrixItem: viewData.roomId, withDisplayName: viewData.roomDisplayName) avatarImageView.contentMode = .scaleAspectFill // bwi: update room avatar if let avatarUrl = viewData.avatarUrl { if !avatarUrl.isEmpty { avatarImageView.enableInMemoryCache = true avatarImageView.setImageURI(avatarUrl, withType: nil, andImageOrientation: .up, toFitViewSize: avatarImageView.frame.size, with: MXThumbnailingMethodScale, previewImage: avatarImage, mediaManager: viewData.mediaManager) } else { avatarImageView.image = avatarImage } } else { avatarImageView.image = avatarImage } if BWIBuildSettings.shared.bwiUseCustomPersonalNotesAvatar { if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { let service = PersonalNotesDefaultService(mxSession: session) if let personalNotesRoomId = service.personalNotesRoomId(), personalNotesRoomId == viewData.roomId { avatarImageView.image = UIImage(named: service.avatarImageUrl()) } } } // bwi: 5216 - federation if BWIBuildSettings.shared.isFederationEnabled && viewData.isFederated { isRoomFederated = true self.roomDisplayName = viewData.roomDisplayName // add imageview for the pill if it doesn't exist if pillImageView == nil { let imageView = FederationIconHelper.shared.getFederatedIconImageView(avatarFrame: avatarImageView.frame) pillImageView = imageView pillImageView?.contentMode = .scaleAspectFit avatarContainerView.addSubview(imageView) } pillImageView?.image = FederationIconHelper.shared.federationBadgeImage() roomNameLabel.attributedText = FederationIconHelper.shared.roomNameWithFederationPill(roomDisplayName: viewData.roomDisplayName, font: roomNameLabel.font) } else { roomNameLabel.text = viewData.roomDisplayName isRoomFederated = false pillImageView?.image = nil } roomNameLabel.numberOfLines = 0 badgeImageView.image = nil if BWIBuildSettings.shared.showMatrixIDinRoomInfoScreen { roomAddressLabel.text = viewData.mainRoomAlias roomAddressLabel.isHidden = roomAddressLabel.text?.isEmpty ?? true } else { roomAddressLabel.text = nil roomAddressLabel.isHidden = true } roomTopicTextView.text = viewData.roomTopic topicContainerView.isHidden = roomTopicTextView.text?.isEmpty ?? true securityTitleLabel.text = VectorL10n.securitySettingsTitle securityInformationLabel.text = viewData.isDirect ? VectorL10n.roomParticipantsSecurityInformationRoomEncryptedForDm : BWIL10n.roomParticipantsSecurityInformationRoomEncrypted securityContainerView.isHidden = !viewData.isEncrypted if let directUserId = viewData.directUserId { presenceIndicatorView.configure(userId: directUserId, presence: viewData.directUserPresence) } else { presenceIndicatorView.stopListeningPresenceUpdates() } updateRoomNameLabelPosition() updateBadgeImageViewPosition(isPresenceDisplayed: viewData.directUserPresence != .unknown) } // bwi private func updateRoomNameLabelPosition() { let margins = self.layoutMarginsGuide roomNameLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true roomNameLabel.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true } private func updateBadgeImageViewPosition(isPresenceDisplayed: Bool) { guard badgeImageView.image != nil else { badgeImageView.isHidden = true return } badgeImageView.isHidden = false // Update badge position if it doesn't match expectation. // If presence is displayed, badge should be in the name stack. let isBadgeInRoomNameStackView = roomNameStackView.arrangedSubviews.contains(badgeImageView) switch (isPresenceDisplayed, isBadgeInRoomNameStackView) { case (true, false): badgeImageView.removeFromSuperview() roomNameStackView.insertArrangedSubview(badgeImageView, at: 0) case (false, true): roomNameStackView.removeArrangedSubview(badgeImageView) avatarContainerView.addSubview(badgeImageView) badgeImageView.trailingAnchor.constraint(equalTo: avatarContainerView.trailingAnchor).isActive = true badgeImageView.bottomAnchor.constraint(equalTo: avatarContainerView.bottomAnchor).isActive = true case (_, _): break } } } extension RoomInfoBasicView: NibLoadable {} extension RoomInfoBasicView: Themable { func update(theme: Theme) { backgroundColor = theme.headerBackgroundColor roomNameLabel.textColor = theme.textPrimaryColor roomAddressLabel.textColor = theme.textSecondaryColor topicTitleLabel.textColor = theme.textSecondaryColor roomTopicTextView.textColor = theme.textPrimaryColor roomTopicTextView.linkTextAttributes = [ NSAttributedString.Key.font: TopicTextViewConstants.font, NSAttributedString.Key.foregroundColor: theme.tintColor ] let mutableReadMore = NSMutableAttributedString(string: "… ", attributes: [ NSAttributedString.Key.font: TopicTextViewConstants.font, NSAttributedString.Key.foregroundColor: theme.textPrimaryColor ]) let attributedMore = NSAttributedString(string: VectorL10n.more, attributes: [ NSAttributedString.Key.font: TopicTextViewConstants.font, NSAttributedString.Key.foregroundColor: theme.tintColor ]) mutableReadMore.append(attributedMore) roomTopicTextView.attributedReadMoreText = mutableReadMore let mutableReadLess = NSMutableAttributedString(string: " ") let attributedLess = NSAttributedString(string: VectorL10n.less, attributes: [ NSAttributedString.Key.font: TopicTextViewConstants.font, NSAttributedString.Key.foregroundColor: theme.tintColor ]) mutableReadLess.append(attributedLess) roomTopicTextView.attributedReadLessText = mutableReadLess securityTitleLabel.textColor = theme.textSecondaryColor securityInformationLabel.textColor = theme.textPrimaryColor presenceIndicatorView.borderColor = theme.headerBackgroundColor // bwi: 5216 - federation if BWIBuildSettings.shared.isFederationEnabled && isRoomFederated { if pillImageView != nil { pillImageView?.image = FederationIconHelper.shared.federationBadgeImage() } if let roomDisplayName = self.roomDisplayName { roomNameLabel.attributedText = FederationIconHelper.shared.roomNameWithFederationPill(roomDisplayName: roomDisplayName, font: roomNameLabel.font) } } } } extension RoomInfoBasicView: PresenceIndicatorViewDelegate { func presenceIndicatorViewDidUpdateVisibility(_ presenceIndicatorView: PresenceIndicatorView, isHidden: Bool) { updateRoomNameLabelPosition() updateBadgeImageViewPosition(isPresenceDisplayed: !isHidden) } }