mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-25 19:10:49 +02:00
b298dedc22
Merge commit 'f823ab9aae70e8d15ed7cc079210dd9bbbb6c8e1' into feature/foss_update_1_11_19 * commit 'f823ab9aae70e8d15ed7cc079210dd9bbbb6c8e1': finish version++ version++ comments update submodule remove obsolete tests removed unused code update submodule fix Libolm removal update license macro update license Prepare for new sprint # Conflicts: # Config/AppVersion.xcconfig # IDETemplateMacros.plist # LICENSE # README.md # Riot/Categories/MXSession+Riot.m # Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift # Riot/Managers/KeyValueStorage/Extensions/Keychain.swift # Riot/Managers/KeyValueStorage/KeyValueStore.swift # Riot/Managers/KeyValueStorage/KeychainStore.swift # Riot/Managers/KeyValueStorage/MemoryStore.swift # Riot/Managers/PushNotification/PushNotificationService.m # Riot/Managers/Settings/RiotSettings.swift # Riot/Managers/Settings/Shared/RiotSharedSettings.swift # Riot/Modules/Analytics/AnalyticsUIElement.swift # Riot/Modules/Application/AppCoordinator.swift # Riot/Modules/Application/LegacyAppDelegate.h # Riot/Modules/Application/LegacyAppDelegate.m # Riot/Modules/Authentication/Legacy/AuthenticationViewController.h # Riot/Modules/Authentication/Legacy/AuthenticationViewController.m # Riot/Modules/Authentication/Legacy/Views/AuthInputsView.h # Riot/Modules/Authentication/Legacy/Views/AuthInputsView.m # Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m # Riot/Modules/Common/Recents/RecentsViewController.m # Riot/Modules/Common/WebViewController/WebViewViewController.m # Riot/Modules/Contacts/Details/ContactDetailsViewController.m # Riot/Modules/Contacts/Views/ContactTableViewCell.m # Riot/Modules/Favorites/FavouritesViewController.h # Riot/Modules/Favorites/FavouritesViewController.m # Riot/Modules/GlobalSearch/UnifiedSearchViewController.m # Riot/Modules/People/PeopleViewController.h # Riot/Modules/People/PeopleViewController.m # Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift # Riot/Modules/Room/DataSources/RoomDataSource.m # Riot/Modules/Room/Files/RoomFilesViewController.m # Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m # Riot/Modules/Room/Members/RoomParticipantsViewController.m # Riot/Modules/Room/RoomViewController.m # Riot/Modules/Room/Settings/RoomSettingsViewController.m # Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroCell.swift # Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroCellContentView.swift # Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroViewData.swift # Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h # Riot/Modules/Rooms/RoomsViewController.h # Riot/Modules/Rooms/ShowDirectory/Cells/Network/DirectoryNetworkTableHeaderFooterView.swift # Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift # Riot/Modules/Rooms/ShowDirectory/PublicRoomsDirectoryViewModel.swift # Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift # Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift # Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift # Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift # Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift # Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift # Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModel.swift # Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModelType.swift # Riot/Modules/SetPinCode/PinCodePreferences.swift # Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift # Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m # Riot/Modules/Settings/Security/SecurityViewController.m # Riot/Modules/Settings/SettingsViewController.m # Riot/Modules/SplitView/SplitViewCoordinator.swift # Riot/Modules/SplitView/SplitViewCoordinatorType.swift # Riot/Modules/StartChat/StartChatViewController.m # Riot/Modules/TabBar/MasterTabBarController.h # Riot/Modules/TabBar/MasterTabBarController.m # Riot/Utils/EventFormatter.m # Riot/Utils/HTMLFormatter.swift # Riot/Utils/Tools.m # RiotNSE/NotificationService.swift
419 lines
14 KiB
Swift
419 lines
14 KiB
Swift
/*
|
|
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
|
|
|
|
@objc protocol BaseRoomCellProtocol: Themable {
|
|
var roomCellContentView: RoomCellContentView? { get }
|
|
}
|
|
|
|
/// `BaseRoomCell` allows a room cell that inherits from this class to embed and manage the default room message outer views and add an inner content view.
|
|
@objcMembers
|
|
class BaseRoomCell: MXKRoomBubbleTableViewCell, BaseRoomCellProtocol {
|
|
|
|
// MARK: - Constants
|
|
|
|
// MARK: - Properties
|
|
|
|
private var areViewsSetup: Bool = false
|
|
|
|
// MARK: Public
|
|
|
|
weak var roomCellContentView: RoomCellContentView?
|
|
|
|
private(set) var theme: Theme?
|
|
|
|
// Overrides
|
|
|
|
override var bubbleInfoContainer: UIView! {
|
|
get {
|
|
guard let infoContainer = self.roomCellContentView?.bubbleInfoContainer else {
|
|
fatalError("[BaseRoomCell] bubbleInfoContainer should not be used before set")
|
|
}
|
|
return infoContainer
|
|
}
|
|
set {
|
|
super.bubbleInfoContainer = newValue
|
|
}
|
|
}
|
|
|
|
override var bubbleOverlayContainer: UIView! {
|
|
get {
|
|
guard let overlayContainer = self.roomCellContentView?.bubbleOverlayContainer else {
|
|
fatalError("[BaseRoomCell] bubbleOverlayContainer should not be used before set")
|
|
}
|
|
return overlayContainer
|
|
}
|
|
set {
|
|
super.bubbleInfoContainer = newValue
|
|
}
|
|
}
|
|
|
|
override var bubbleInfoContainerTopConstraint: NSLayoutConstraint! {
|
|
get {
|
|
guard let infoContainerTopConstraint = self.roomCellContentView?.bubbleInfoContainerTopConstraint else {
|
|
fatalError("[BaseRoomCell] bubbleInfoContainerTopConstraint should not be used before set")
|
|
}
|
|
return infoContainerTopConstraint
|
|
}
|
|
set {
|
|
super.bubbleInfoContainerTopConstraint = newValue
|
|
}
|
|
}
|
|
|
|
override var pictureView: MXKImageView! {
|
|
get {
|
|
guard let roomCellContentView = self.roomCellContentView,
|
|
roomCellContentView.showSenderAvatar else {
|
|
return nil
|
|
}
|
|
|
|
guard let pictureView = self.roomCellContentView?.avatarImageView else {
|
|
fatalError("[BaseRoomCell] pictureView should not be used before set")
|
|
}
|
|
return pictureView
|
|
}
|
|
set {
|
|
super.pictureView = newValue
|
|
}
|
|
}
|
|
|
|
override var userNameLabel: UILabel! {
|
|
get {
|
|
guard let roomCellContentView = self.roomCellContentView, roomCellContentView.showSenderName else {
|
|
return nil
|
|
}
|
|
|
|
guard let userNameLabel = roomCellContentView.userNameLabel else {
|
|
fatalError("[BaseRoomCell] userNameLabel should not be used before set")
|
|
}
|
|
return userNameLabel
|
|
}
|
|
set {
|
|
super.userNameLabel = newValue
|
|
}
|
|
}
|
|
|
|
override var userNameTapGestureMaskView: UIView! {
|
|
get {
|
|
guard let roomCellContentView = self.roomCellContentView,
|
|
roomCellContentView.showSenderName else {
|
|
return nil
|
|
}
|
|
|
|
guard let userNameTapGestureMaskView = self.roomCellContentView?.userNameTouchMaskView else {
|
|
fatalError("[BaseRoomCell] userNameTapGestureMaskView should not be used before set")
|
|
}
|
|
return userNameTapGestureMaskView
|
|
}
|
|
set {
|
|
super.userNameTapGestureMaskView = newValue
|
|
}
|
|
}
|
|
|
|
override var readMarkerViewLeadingConstraint: NSLayoutConstraint? {
|
|
get {
|
|
if self is RoomCellReadMarkerDisplayable {
|
|
return self.roomCellContentView?.readMarkerViewLeadingConstraint
|
|
} else {
|
|
return super.readMarkerViewLeadingConstraint
|
|
}
|
|
}
|
|
set {
|
|
if self is RoomCellReadMarkerDisplayable {
|
|
self.roomCellContentView?.readMarkerViewLeadingConstraint = newValue
|
|
} else {
|
|
super.readMarkerViewLeadingConstraint = newValue
|
|
}
|
|
}
|
|
}
|
|
|
|
override var readMarkerViewTrailingConstraint: NSLayoutConstraint? {
|
|
get {
|
|
if self is RoomCellReadMarkerDisplayable {
|
|
return self.roomCellContentView?.readMarkerViewTrailingConstraint
|
|
} else {
|
|
return super.readMarkerViewTrailingConstraint
|
|
}
|
|
}
|
|
set {
|
|
if self is RoomCellReadMarkerDisplayable {
|
|
self.roomCellContentView?.readMarkerViewTrailingConstraint = newValue
|
|
} else {
|
|
super.readMarkerViewTrailingConstraint = newValue
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Setup
|
|
|
|
required override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
self.commonInit()
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
self.commonInit()
|
|
}
|
|
|
|
private func commonInit() {
|
|
self.selectionStyle = .none
|
|
self.setupContentView()
|
|
self.update(theme: ThemeService.shared().theme)
|
|
}
|
|
|
|
// MARK: - Public
|
|
|
|
func removeDecorationViews() {
|
|
if let roomCellReadReceiptsDisplayable = self as? RoomCellReadReceiptsDisplayable {
|
|
roomCellReadReceiptsDisplayable.removeReadReceiptsView()
|
|
}
|
|
|
|
if let roomCellReactionsDisplayable = self as? RoomCellReactionsDisplayable {
|
|
roomCellReactionsDisplayable.removeReactionsView()
|
|
}
|
|
|
|
if let roomCellThreadSummaryDisplayable = self as? RoomCellThreadSummaryDisplayable {
|
|
roomCellThreadSummaryDisplayable.removeThreadSummaryView()
|
|
}
|
|
|
|
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 = roomCellContentView?.showEncryptionStatus ?? false
|
|
|
|
if showEncryptionStatus {
|
|
self.setupEncryptionStatusViewTapGestureRecognizer()
|
|
}
|
|
}
|
|
|
|
override func setupSenderNameLabel() {
|
|
|
|
guard let userNameTouchMaskView = self.roomCellContentView?.userNameTouchMaskView else {
|
|
return
|
|
}
|
|
|
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onSenderNameTap(_:)))
|
|
tapGesture.numberOfTouchesRequired = 1
|
|
tapGesture.numberOfTapsRequired = 1
|
|
tapGesture.delegate = self
|
|
|
|
userNameTouchMaskView.addGestureRecognizer(tapGesture)
|
|
}
|
|
|
|
override func setupAvatarView() {
|
|
|
|
guard let avatarImageView = self.roomCellContentView?.avatarImageView else {
|
|
return
|
|
}
|
|
|
|
avatarImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder
|
|
|
|
// Listen to avatar tap
|
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onAvatarTap(_:)))
|
|
tapGesture.numberOfTouchesRequired = 1
|
|
tapGesture.numberOfTapsRequired = 1
|
|
tapGesture.delegate = self
|
|
avatarImageView.addGestureRecognizer(tapGesture)
|
|
avatarImageView.isUserInteractionEnabled = true
|
|
|
|
// Add a long gesture recognizer on avatar (in order to display for example the member details)
|
|
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(onLongPressGesture(_:)))
|
|
avatarImageView.addGestureRecognizer(longPress)
|
|
}
|
|
|
|
override class func defaultReuseIdentifier() -> String! {
|
|
return String(describing: self)
|
|
}
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
|
|
self.removeDecorationViews()
|
|
}
|
|
|
|
override func render(_ cellData: MXKCellData!) {
|
|
// In `MXKRoomBubbleTableViewCell` setupViews() is called in awakeFromNib() that is not called here, so call it only on first render() call
|
|
self.setupViewsIfNeeded()
|
|
|
|
super.render(cellData)
|
|
|
|
guard let roomCellContentView = self.roomCellContentView else {
|
|
return
|
|
}
|
|
|
|
if let bubbleData = self.bubbleData,
|
|
let paginationDate = bubbleData.date,
|
|
roomCellContentView.showPaginationTitle {
|
|
roomCellContentView.paginationLabel.text = bubbleData.eventFormatter.dateString(from: paginationDate, withTime: false)?.uppercased()
|
|
}
|
|
|
|
// BWI: #5223 add federation icon in timeline
|
|
if let bubbleData = self.bubbleData {
|
|
if let room = bubbleData.mxSession.room(withRoomId: bubbleData.roomId) {
|
|
self.roomCellContentView?.showFederationPill = room.isRoomMemberFederated(bubbleData.senderId)
|
|
}
|
|
}
|
|
|
|
if roomCellContentView.showEncryptionStatus {
|
|
self.updateEncryptionStatusViewImage()
|
|
}
|
|
|
|
self.updateUserNameColor()
|
|
}
|
|
|
|
override func customizeRendering() {
|
|
super.customizeRendering()
|
|
self.updateUserNameColor()
|
|
}
|
|
|
|
// MARK: - Themable
|
|
|
|
func update(theme: Theme) {
|
|
self.theme = theme
|
|
self.roomCellContentView?.update(theme: theme)
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func setupViewsIfNeeded() {
|
|
guard self.areViewsSetup == false else {
|
|
return
|
|
}
|
|
self.setupViews()
|
|
self.areViewsSetup = true
|
|
}
|
|
|
|
private func setupContentView() {
|
|
guard self.roomCellContentView == nil else {
|
|
return
|
|
}
|
|
let roomCellContentView = RoomCellContentView.instantiate()
|
|
self.contentView.vc_addSubViewMatchingParent(roomCellContentView)
|
|
self.roomCellContentView = roomCellContentView
|
|
}
|
|
|
|
// MARK: - RoomCellURLPreviewDisplayable
|
|
// Cannot use default implementation with ObjC protocol, if self conforms to RoomCellURLPreviewDisplayable method below will be used
|
|
|
|
func addURLPreviewView(_ urlPreviewView: UIView) {
|
|
self.roomCellContentView?.addURLPreviewView(urlPreviewView)
|
|
|
|
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
|
|
self.addTemporarySubview(urlPreviewView)
|
|
}
|
|
|
|
func removeURLPreviewView() {
|
|
self.roomCellContentView?.removeURLPreviewView()
|
|
}
|
|
|
|
// MARK: - RoomCellReadReceiptsDisplayable
|
|
// Cannot use default implementation with ObjC protocol, if self conforms to RoomCellReadReceiptsDisplayable method below will be used
|
|
|
|
func addReadReceiptsView(_ readReceiptsView: UIView) {
|
|
self.roomCellContentView?.addReadReceiptsView(readReceiptsView)
|
|
|
|
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
|
|
self.addTemporarySubview(readReceiptsView)
|
|
}
|
|
|
|
func removeReadReceiptsView() {
|
|
self.roomCellContentView?.removeReadReceiptsView()
|
|
}
|
|
|
|
// MARK: - RoomCellReactionsDisplayable
|
|
// Cannot use default implementation with ObjC protocol, if self conforms to RoomCellReactionsDisplayable method below will be used
|
|
|
|
func addReactionsView(_ reactionsView: UIView) {
|
|
self.roomCellContentView?.addReactionsView(reactionsView)
|
|
|
|
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
|
|
self.addTemporarySubview(reactionsView)
|
|
}
|
|
|
|
func removeReactionsView() {
|
|
self.roomCellContentView?.removeReactionsView()
|
|
}
|
|
|
|
// MARK: - RoomCellThreadSummaryDisplayable
|
|
|
|
func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView) {
|
|
self.roomCellContentView?.addThreadSummaryView(threadSummaryView)
|
|
|
|
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
|
|
self.addTemporarySubview(threadSummaryView)
|
|
}
|
|
|
|
func removeThreadSummaryView() {
|
|
self.roomCellContentView?.removeThreadSummaryView()
|
|
}
|
|
|
|
// MARK: - RoomCellReadMarkerDisplayable
|
|
|
|
func addReadMarkerView(_ readMarkerView: UIView) {
|
|
self.roomCellContentView?.addReadMarkerView(readMarkerView)
|
|
self.readMarkerView = readMarkerView
|
|
}
|
|
|
|
override func removeReadMarkerView() {
|
|
self.roomCellContentView?.removeReadMarkerView()
|
|
|
|
super.removeReadMarkerView()
|
|
}
|
|
|
|
// Encryption status
|
|
|
|
private func updateEncryptionStatusViewImage() {
|
|
guard let component = self.bubbleData.getFirstBubbleComponentWithDisplay() else {
|
|
return
|
|
}
|
|
self.roomCellContentView?.encryptionImageView.image = RoomEncryptedDataBubbleCell.encryptionIcon(for: component)
|
|
}
|
|
|
|
private func setupEncryptionStatusViewTapGestureRecognizer() {
|
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleEncryptionStatusContainerViewTap(_:)))
|
|
tapGestureRecognizer.delegate = self
|
|
self.roomCellContentView?.encryptionImageView.isUserInteractionEnabled = true
|
|
}
|
|
|
|
@objc private func handleEncryptionStatusContainerViewTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
|
guard let delegate = self.delegate else {
|
|
return
|
|
}
|
|
|
|
guard let component = self.bubbleData.getFirstBubbleComponentWithDisplay() else {
|
|
return
|
|
}
|
|
|
|
let userInfo: [AnyHashable: Any]?
|
|
|
|
if let tappedEvent = component.event {
|
|
userInfo = [kMXKRoomBubbleCellEventKey: tappedEvent]
|
|
} else {
|
|
userInfo = nil
|
|
}
|
|
|
|
delegate.cell(self, didRecognizeAction: kRoomEncryptedDataBubbleCellTapOnEncryptionIcon, userInfo: userInfo)
|
|
}
|
|
}
|