diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index 0d6b6689a..738454280 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -44,7 +44,13 @@ jobs: name: P1 X-Needs-Design to Design project board runs-on: ubuntu-latest if: > - contains(github.event.issue.labels.*.name, 'X-Needs-Design') + contains(github.event.issue.labels.*.name, 'X-Needs-Design') && + (contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'O-Occasional')) || + (contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) || + contains(github.event.issue.labels.*.name, 'A11y')) steps: - uses: octokit/graphql-action@v2.x id: add_to_project @@ -202,3 +208,81 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc4AArk0" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features1: + name: Add labelled issues to PS features team 1 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-Polls') || + contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || + (contains(github.event.issue.labels.*.name, 'A-Voice-Messages') && + !contains(github.event.issue.labels.*.name, 'A-Broadcast')) || + (contains(github.event.issue.labels.*.name, 'A-Session-Mgmt') && + contains(github.event.issue.labels.*.name, 'A-User-Settings')) + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKF" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features2: + name: Add labelled issues to PS features team 2 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-DM-Start') || + contains(github.event.issue.labels.*.name, 'A-Broadcast') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKd" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features3: + name: Add labelled issues to PS features team 3 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-Composer-WYSIWYG') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKW" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 2b68178be..aa0de1873 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -234,6 +234,8 @@ final class BuildSettings: NSObject { static let allowInviteExernalUsers: Bool = true + static let allowBackgroundAudioMessagePlayback: Bool = true + // MARK: - Side Menu static let enableSideMenu: Bool = true && !newAppLayoutEnabled static let sideMenuShowInviteFriends: Bool = true diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 482bfb1c9..ef28187e4 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,7 +23,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "11dad16e3e589dba423f6cc5707e9df8aace89b0" + "revision" : "d5ef7054fb43924d5b92d5d627347ca2bc333717" } }, { diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index dc87963f7..fdb8d22d9 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2625,3 +2625,4 @@ "authentication_qr_login_start_subtitle" = "Használd a kamerát ezen az eszközön a másik eszközödön megjelenő QR kód beolvasására:"; "authentication_qr_login_start_title" = "QR kód beolvasása"; "authentication_login_with_qr" = "Belépés QR kóddal"; +"settings_labs_enable_voice_broadcast" = "Hang közvetítés (aktív fejlesztés alatt). Jelenleg a hang közvetítést csak a szoba idővonalán jelezzük, egyenlőre nem lehet hangot sugározni vagy belehallgatni a közvetítésbe"; diff --git a/Riot/Categories/UITableViewCell.swift b/Riot/Categories/UITableViewCell.swift index 86c4b7ee0..6071a11ec 100644 --- a/Riot/Categories/UITableViewCell.swift +++ b/Riot/Categories/UITableViewCell.swift @@ -51,5 +51,16 @@ extension UITableViewCell { @objc func vc_setAccessoryDisclosureIndicatorWithCurrentTheme() { self.vc_setAccessoryDisclosureIndicator(withTheme: ThemeService.shared().theme) } + + @objc var vc_parentViewController: UIViewController? { + var parent: UIResponder? = self + while parent != nil { + parent = parent?.next + if let viewController = parent as? UIViewController { + return viewController + } + } + return nil + } } diff --git a/Riot/Modules/Common/Avatar/AvatarView.swift b/Riot/Modules/Common/Avatar/AvatarView.swift index e222d5af9..7cee21d40 100644 --- a/Riot/Modules/Common/Avatar/AvatarView.swift +++ b/Riot/Modules/Common/Avatar/AvatarView.swift @@ -106,19 +106,9 @@ class AvatarView: UIView, Themable { return } - let defaultAvatarImage: UIImage? - var defaultAvatarImageContentMode: UIView.ContentMode = .scaleAspectFill + let (defaultAvatarImage, defaultAvatarImageContentMode) = viewData.fallbackImageParameters() ?? (nil, .scaleAspectFill) + updateAvatarImageView(image: defaultAvatarImage, contentMode: defaultAvatarImageContentMode) - switch viewData.fallbackImage { - case .matrixItem(let matrixItemId, let matrixItemDisplayName): - defaultAvatarImage = AvatarGenerator.generateAvatar(forMatrixItem: matrixItemId, withDisplayName: matrixItemDisplayName) - case .image(let image, let contentMode): - defaultAvatarImage = image - defaultAvatarImageContentMode = contentMode ?? .scaleAspectFill - case .none: - defaultAvatarImage = nil - } - if let avatarUrl = viewData.avatarUrl { avatarImageView.setImageURI(avatarUrl, withType: nil, @@ -127,12 +117,9 @@ class AvatarView: UIView, Themable { with: MXThumbnailingMethodScale, previewImage: defaultAvatarImage, mediaManager: viewData.mediaManager) - avatarImageView.contentMode = .scaleAspectFill - avatarImageView.imageView?.contentMode = .scaleAspectFill + updateAvatarContentMode(contentMode: .scaleAspectFill) } else { - avatarImageView.image = defaultAvatarImage - avatarImageView.contentMode = defaultAvatarImageContentMode - avatarImageView.imageView?.contentMode = defaultAvatarImageContentMode + updateAvatarImageView(image: defaultAvatarImage, contentMode: defaultAvatarImageContentMode) } } @@ -148,6 +135,16 @@ class AvatarView: UIView, Themable { gestureRecognizer.minimumPressDuration = 0 self.addGestureRecognizer(gestureRecognizer) } + + private func updateAvatarImageView(image: UIImage?, contentMode: UIView.ContentMode) { + avatarImageView?.image = image + updateAvatarContentMode(contentMode: contentMode) + } + + private func updateAvatarContentMode(contentMode: UIView.ContentMode) { + avatarImageView?.contentMode = contentMode + avatarImageView?.imageView.contentMode = contentMode + } // MARK: - Actions diff --git a/Riot/Modules/Common/Avatar/AvatarViewData.swift b/Riot/Modules/Common/Avatar/AvatarViewData.swift index ef5cbb89c..88eb47a07 100644 --- a/Riot/Modules/Common/Avatar/AvatarViewData.swift +++ b/Riot/Modules/Common/Avatar/AvatarViewData.swift @@ -29,6 +29,21 @@ struct AvatarViewData: AvatarViewDataProtocol { /// Matrix media handler if exists var mediaManager: MXMediaManager? - /// Fallback image used when avatarUrl is nil - var fallbackImage: AvatarFallbackImage? + /// Fallback images used when avatarUrl is nil + var fallbackImages: [AvatarFallbackImage]? +} + +extension AvatarViewData { + init(matrixItemId: String, + displayName: String? = nil, + avatarUrl: String? = nil, + mediaManager: MXMediaManager? = nil, + fallbackImage: AvatarFallbackImage?) { + + self.matrixItemId = matrixItemId + self.displayName = displayName + self.avatarUrl = avatarUrl + self.mediaManager = mediaManager + self.fallbackImages = fallbackImage.map { [$0] } + } } diff --git a/Riot/Modules/Common/Avatar/AvatarViewDataProtocol.swift b/Riot/Modules/Common/Avatar/AvatarViewDataProtocol.swift index 9b677e581..f3410783b 100644 --- a/Riot/Modules/Common/Avatar/AvatarViewDataProtocol.swift +++ b/Riot/Modules/Common/Avatar/AvatarViewDataProtocol.swift @@ -41,6 +41,24 @@ protocol AvatarViewDataProtocol: AvatarProtocol { /// Matrix media handler var mediaManager: MXMediaManager? { get } - /// Fallback image used when avatarUrl is nil - var fallbackImage: AvatarFallbackImage? { get } + /// Fallback images used when avatarUrl is nil + var fallbackImages: [AvatarFallbackImage]? { get } +} + +extension AvatarViewDataProtocol { + func fallbackImageParameters() -> (UIImage?, UIView.ContentMode)? { + fallbackImages? + .lazy + .map { fallbackImage in + switch fallbackImage { + case .matrixItem(let matrixItemId, let matrixItemDisplayName): + return (AvatarGenerator.generateAvatar(forMatrixItem: matrixItemId, withDisplayName: matrixItemDisplayName), .scaleAspectFill) + case .image(let image, let contentMode): + return (image, contentMode ?? .scaleAspectFill) + } + } + .first { (image, contentMode) in + image != nil + } + } } diff --git a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift index 141c676e2..ef1c2a66d 100644 --- a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift +++ b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift @@ -25,8 +25,7 @@ import Combine class VectorHostingController: UIHostingController { // MARK: Private - - private let forceZeroSafeAreaInsets: Bool + private var theme: Theme private var heightSubject = CurrentValueSubject(0) @@ -55,11 +54,8 @@ class VectorHostingController: UIHostingController { } /// Initializer /// - Parameter rootView: Root view for the controller. - /// - Parameter forceZeroSafeAreaInsets: Whether to force-set the hosting view's safe area insets to zero. Useful when the view is used as part of a table view. - init(rootView: Content, - forceZeroSafeAreaInsets: Bool = false) where Content: View { + init(rootView: Content) where Content: View { self.theme = ThemeService.shared().theme - self.forceZeroSafeAreaInsets = forceZeroSafeAreaInsets super.init(rootView: AnyView(rootView.vectorContent())) } @@ -116,22 +112,6 @@ class VectorHostingController: UIHostingController { heightSubject.send(height) } } - - override func viewSafeAreaInsetsDidChange() { - super.viewSafeAreaInsetsDidChange() - - guard forceZeroSafeAreaInsets else { - return - } - - let counterSafeAreaInsets = UIEdgeInsets(top: -view.safeAreaInsets.top, - left: -view.safeAreaInsets.left, - bottom: -view.safeAreaInsets.bottom, - right: -view.safeAreaInsets.right) - if additionalSafeAreaInsets != counterSafeAreaInsets, counterSafeAreaInsets != .zero { - additionalSafeAreaInsets = counterSafeAreaInsets - } - } private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 932a52333..1070db4e5 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -332,7 +332,7 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { createAvatarButtonItem(for: viewController) } - private func createAvatarButtonItem(for viewController: UIViewController) { + private var avatarMenu: UIMenu { var actions: [UIMenuElement] = [] actions.append(UIAction(title: VectorL10n.allChatsUserMenuSettings, image: UIImage(systemName: "gearshape")) { [weak self] action in @@ -358,32 +358,30 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { } ])) - let menu = UIMenu(options: .displayInline, children: actions) - + return UIMenu(options: .displayInline, children: actions) + } + + private func createAvatarButtonItem(for viewController: UIViewController) { let view = UIView(frame: CGRect(x: 0, y: 0, width: 36, height: 36)) view.backgroundColor = .clear - let button: UIButton = UIButton(frame: view.bounds.inset(by: UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7))) + let avatarInsets: UIEdgeInsets = .init(top: 7, left: 7, bottom: 7, right: 7) + let button: UIButton = .init(frame: view.bounds.inset(by: avatarInsets)) button.setImage(Asset.Images.tabPeople.image, for: .normal) - button.menu = menu + button.menu = avatarMenu button.showsMenuAsPrimaryAction = true button.autoresizingMask = [.flexibleHeight, .flexibleWidth] button.accessibilityLabel = VectorL10n.allChatsUserMenuAccessibilityLabel view.addSubview(button) self.avatarMenuButton = button - let avatarView = UserAvatarView(frame: view.bounds.inset(by: UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7))) + let avatarView = UserAvatarView(frame: view.bounds.inset(by: avatarInsets)) avatarView.isUserInteractionEnabled = false avatarView.update(theme: ThemeService.shared().theme) avatarView.autoresizingMask = [.flexibleHeight, .flexibleWidth] view.addSubview(avatarView) self.avatarMenuView = avatarView - - if let avatar = userAvatarViewData(from: currentMatrixSession) { - avatarView.fill(with: avatar) - button.setImage(nil, for: .normal) - } - + updateAvatarButtonItem() viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: view) } diff --git a/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsAvatarView.swift b/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsAvatarView.swift index 744bfa2b6..caab08a86 100644 --- a/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsAvatarView.swift +++ b/Riot/Modules/Room/NotificationSettings/UIKit/RoomNotificationSettingsAvatarView.swift @@ -25,7 +25,7 @@ class RoomNotificationSettingsAvatarView: UIView { func configure(viewData: AvatarViewDataProtocol) { avatarView.fill(with: viewData) - switch viewData.fallbackImage { + switch viewData.fallbackImages?.first { case .matrixItem(_, let matrixItemDisplayName): nameLabel.text = matrixItemDisplayName default: diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index b3911f884..a115fda85 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1998,9 +1998,9 @@ static CGSize kThreadListBarButtonItemImageSize; [self updateInputToolBarVisibility]; // Check whether the input toolbar is ready before updating it. - if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) + if (self.inputToolbarView && [self inputToolbarConformsToToolbarViewProtocol]) { - RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + id roomInputToolbarView = (id) self.inputToolbarView; // Update encryption decoration if needed [self updateEncryptionDecorationForRoomInputToolbar:roomInputToolbarView]; @@ -2120,9 +2120,9 @@ static CGSize kThreadListBarButtonItemImageSize; - (void)updateInputToolbarEncryptionDecoration { - if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) + if (self.inputToolbarView && [self inputToolbarConformsToToolbarViewProtocol]) { - RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + id roomInputToolbarView = (id)self.inputToolbarView; [self updateEncryptionDecorationForRoomInputToolbar:roomInputToolbarView]; } } @@ -2138,7 +2138,7 @@ static CGSize kThreadListBarButtonItemImageSize; roomTitleView.badgeImageView.image = self.roomEncryptionBadgeImage; } -- (void)updateEncryptionDecorationForRoomInputToolbar:(RoomInputToolbarView*)roomInputToolbarView +- (void)updateEncryptionDecorationForRoomInputToolbar:(id)roomInputToolbarView { roomInputToolbarView.isEncryptionEnabled = self.isEncryptionEnabled; } @@ -4593,6 +4593,9 @@ static CGSize kThreadListBarButtonItemImageSize; // Do nothing for dummy links shouldDoAction = NO; break; + case RoomMessageURLTypeHttp: + shouldDoAction = YES; + break; default: { MXEvent *tappedEvent = userInfo[kMXKRoomBubbleCellEventKey]; @@ -4618,16 +4621,20 @@ static CGSize kThreadListBarButtonItemImageSize; break; case UITextItemInteractionPresentActions: { - // Retrieve the tapped event - MXEvent *tappedEvent = userInfo[kMXKRoomBubbleCellEventKey]; - - if (tappedEvent) - { - // Long press on link, present room contextual menu. - [self showContextualMenuForEvent:tappedEvent fromSingleTapGesture:NO cell:cell animated:YES]; + if (roomMessageURLType == RoomMessageURLTypeHttp) { + shouldDoAction = YES; + } else { + // Retrieve the tapped event + MXEvent *tappedEvent = userInfo[kMXKRoomBubbleCellEventKey]; + + if (tappedEvent) + { + // Long press on link, present room contextual menu. + [self showContextualMenuForEvent:tappedEvent fromSingleTapGesture:NO cell:cell animated:YES]; + } + + shouldDoAction = NO; } - - shouldDoAction = NO; } break; case UITextItemInteractionPreview: @@ -5026,12 +5033,12 @@ static CGSize kThreadListBarButtonItemImageSize; if (self.roomInputToolbarContainerHeightConstraint.constant != height) { // Hide temporarily the placeholder to prevent its distortion during height animation - if (!savedInputToolbarPlaceholder) + if (toolbarView.placeholder.length) { - savedInputToolbarPlaceholder = toolbarView.placeholder.length ? toolbarView.placeholder : @""; + savedInputToolbarPlaceholder = toolbarView.placeholder; + toolbarView.placeholder = nil; } - toolbarView.placeholder = nil; - + [super roomInputToolbarView:toolbarView heightDidChanged:height completion:^(BOOL finished) { if (completion) @@ -5040,10 +5047,10 @@ static CGSize kThreadListBarButtonItemImageSize; } // Consider here the saved placeholder only if no new placeholder has been defined during the height animation. - if (!toolbarView.placeholder) + if (!toolbarView.placeholder && self->savedInputToolbarPlaceholder.length) { // Restore the placeholder if any - toolbarView.placeholder = self->savedInputToolbarPlaceholder.length ? self->savedInputToolbarPlaceholder : nil; + toolbarView.placeholder = self->savedInputToolbarPlaceholder; } self->savedInputToolbarPlaceholder = nil; }]; diff --git a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m index 13eac3bcc..1b39e9822 100644 --- a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m +++ b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m @@ -237,6 +237,7 @@ static BOOL _disableLongPressGestureOnEvent; [tapGesture setDelegate:self]; [self.messageTextView addGestureRecognizer:tapGesture]; self.messageTextView.userInteractionEnabled = YES; + self.messageTextView.clipsToBounds = NO; // Recognise and make tappable phone numbers, address, etc. self.messageTextView.dataDetectorTypes = UIDataDetectorTypeAll; diff --git a/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift b/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift index 5aa5f10e5..f33762144 100644 --- a/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift +++ b/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift @@ -16,6 +16,7 @@ import UIKit import MatrixSDK +import SwiftUI @objc protocol SizableBaseRoomCellType: BaseRoomCellProtocol { static func sizingViewHeightHashValue(from bubbleCellData: MXKRoomBubbleCellData) -> Int @@ -35,6 +36,7 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { private static let reactionsViewModelBuilder = RoomReactionsViewModelBuilder() private static let urlPreviewViewSizer = URLPreviewViewSizer() + private var contentVC: UIViewController? private class var sizingView: SizableBaseRoomCell { let sizingView: SizableBaseRoomCell @@ -115,6 +117,10 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { sizingView.setNeedsLayout() sizingView.layoutIfNeeded() + if let contentVC = sizingView.contentVC as? UIHostingController { + contentVC.view.invalidateIntrinsicContentSize() + } + let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height) var height = sizingView.systemLayoutSizeFitting(fittingSize).height @@ -168,4 +174,24 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { return height } + + func addContentViewController(_ controller: UIViewController, on contentView: UIView) { + controller.view.invalidateIntrinsicContentSize() + + let parent = vc_parentViewController + parent?.addChild(controller) + contentView.vc_addSubViewMatchingParent(controller.view) + controller.didMove(toParent: parent) + + contentVC = controller + } + + override func prepareForReuse() { + contentVC?.removeFromParent() + contentVC?.view.removeFromSuperview() + contentVC?.didMove(toParent: nil) + contentVC = nil + + super.prepareForReuse() + } } diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift index b69abdcd4..993b606c5 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift @@ -36,10 +36,10 @@ class PollBaseBubbleCell: PollPlainCell { self.setupBubbleBackgroundView() } - override func addPollView(_ pollView: UIView, on contentView: UIView) { - super.addPollView(pollView, on: contentView) + override func addContentViewController(_ controller: UIViewController, on contentView: UIView) { + super.addContentViewController(controller, on: contentView) - self.addBubbleBackgroundViewIfNeeded(for: pollView) + self.addBubbleBackgroundViewIfNeeded(for: controller.view) } // MARK: - Private diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift index a05f00285..67db62e88 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift @@ -36,10 +36,10 @@ class VoiceBroadcastBubbleCell: VoiceBroadcastPlainCell { self.setupBubbleBackgroundView() } - override func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) { - super.addVoiceBroadcastView(voiceBroadcastView, on: contentView) - - self.addBubbleBackgroundViewIfNeeded(for: voiceBroadcastView) + override func addContentViewController(_ controller: UIViewController, on contentView: UIView) { + super.addContentViewController(controller, on: contentView) + + self.addBubbleBackgroundViewIfNeeded(for: controller.view) } // MARK: - Private diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift index 345f0de95..70cf4370f 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift @@ -17,8 +17,7 @@ import Foundation class PollPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable { - - private var pollView: UIView? + private var event: MXEvent? override func render(_ cellData: MXKCellData!) { @@ -28,12 +27,12 @@ class PollPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCell let bubbleData = cellData as? RoomBubbleCellData, let event = bubbleData.events.last, event.eventType == __MXEventType.pollStart, - let view = TimelinePollProvider.shared.buildTimelinePollViewForEvent(event) else { + let controller = TimelinePollProvider.shared.buildTimelinePollVCForEvent(event) else { return } self.event = event - self.addPollView(view, on: contentView) + self.addContentViewController(controller, on: contentView) } override func setupViews() { @@ -52,13 +51,6 @@ class PollPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCell delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event]) } - - func addPollView(_ pollView: UIView, on contentView: UIView) { - - self.pollView?.removeFromSuperview() - contentView.vc_addSubViewMatchingParent(pollView) - self.pollView = pollView - } } extension PollPlainCell: RoomCellThreadSummaryDisplayable {} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift index 8f9666a83..14c602c4c 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift @@ -18,7 +18,6 @@ import Foundation class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable { - private var voiceBroadcastView: UIView? private var event: MXEvent? override func render(_ cellData: MXKCellData!) { @@ -29,12 +28,12 @@ class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable let event = bubbleData.events.last, let voiceBroadcastContent = VoiceBroadcastInfo(fromJSON: event.content), voiceBroadcastContent.state == VoiceBroadcastInfo.State.started.rawValue, - let view = VoiceBroadcastPlaybackProvider.shared.buildVoiceBroadcastPlaybackViewForEvent(event, senderDisplayName: bubbleData.senderDisplayName) else { + let controller = VoiceBroadcastPlaybackProvider.shared.buildVoiceBroadcastPlaybackVCForEvent(event, senderDisplayName: bubbleData.senderDisplayName) else { return } self.event = event - self.addVoiceBroadcastView(view, on: contentView) + self.addContentViewController(controller, on: contentView) } override func setupViews() { @@ -53,13 +52,6 @@ class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event]) } - - func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) { - - self.voiceBroadcastView?.removeFromSuperview() - contentView.vc_addSubViewMatchingParent(voiceBroadcastView) - self.voiceBroadcastView = voiceBroadcastView - } } extension VoiceBroadcastPlainCell: RoomCellThreadSummaryDisplayable {} diff --git a/Riot/Modules/Room/Views/Avatar/RoomAvatarViewData.swift b/Riot/Modules/Room/Views/Avatar/RoomAvatarViewData.swift index 1af0a99aa..7f8df4e18 100644 --- a/Riot/Modules/Room/Views/Avatar/RoomAvatarViewData.swift +++ b/Riot/Modules/Room/Views/Avatar/RoomAvatarViewData.swift @@ -26,7 +26,7 @@ struct RoomAvatarViewData: AvatarViewDataProtocol { return roomId } - var fallbackImage: AvatarFallbackImage? { - return .matrixItem(matrixItemId, displayName) + var fallbackImages: [AvatarFallbackImage]? { + [.matrixItem(matrixItemId, displayName)] } } diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 72341ff2a..4bdea353b 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -37,6 +37,7 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode) @property (nonatomic, strong) NSString *eventSenderDisplayName; @property (nonatomic, assign) RoomInputToolbarViewSendMode sendMode; +@property (nonatomic, assign) BOOL isEncryptionEnabled; - (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView; - (CGFloat)toolbarHeight; @@ -80,7 +81,7 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode) `RoomInputToolbarView` instance is a view used to handle all kinds of available inputs for a room (message composer, attachments selection...). */ -@interface RoomInputToolbarView : MXKRoomInputToolbarView +@interface RoomInputToolbarView : MXKRoomInputToolbarView /** The delegate notified when inputs are ready. diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index cd9195516..9abfde421 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -19,7 +19,6 @@ #import "ThemeService.h" #import "GeneratedInterface-Swift.h" -#import "GBDeviceInfo_iOS.h" static const CGFloat kContextBarHeight = 24; static const CGFloat kActionMenuAttachButtonSpringVelocity = 7; @@ -30,7 +29,7 @@ static const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; static const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; -@interface RoomInputToolbarView() +@interface RoomInputToolbarView() @property (nonatomic, weak) IBOutlet UIView *mainToolbarView; @@ -281,69 +280,6 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; } } -- (void)updatePlaceholder -{ - // Consider the default placeholder - - NSString *placeholder; - - // Check the device screen size before using large placeholder - BOOL shouldDisplayLargePlaceholder = [GBDeviceInfo deviceInfo].family == GBDeviceFamilyiPad || [GBDeviceInfo deviceInfo].displayInfo.display >= GBDeviceDisplay5p8Inch; - - if (!shouldDisplayLargePlaceholder) - { - switch (_sendMode) - { - case RoomInputToolbarViewSendModeReply: - placeholder = [VectorL10n roomMessageReplyToShortPlaceholder]; - break; - - case RoomInputToolbarViewSendModeCreateDM: - placeholder = [VectorL10n roomFirstMessagePlaceholder]; - break; - - default: - placeholder = [VectorL10n roomMessageShortPlaceholder]; - break; - } - } - else - { - if (_isEncryptionEnabled) - { - switch (_sendMode) - { - case RoomInputToolbarViewSendModeReply: - placeholder = [VectorL10n encryptedRoomMessageReplyToPlaceholder]; - break; - - default: - placeholder = [VectorL10n encryptedRoomMessagePlaceholder]; - break; - } - } - else - { - switch (_sendMode) - { - case RoomInputToolbarViewSendModeReply: - placeholder = [VectorL10n roomMessageReplyToPlaceholder]; - break; - - case RoomInputToolbarViewSendModeCreateDM: - placeholder = [VectorL10n roomFirstMessagePlaceholder]; - break; - - default: - placeholder = [VectorL10n roomMessagePlaceholder]; - break; - } - } - } - - self.placeholder = placeholder; -} - - (void)setPlaceholder:(NSString *)inPlaceholder { [super setPlaceholder:inPlaceholder]; diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.swift b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.swift index 6a9de2f30..045fcc9a4 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.swift +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.swift @@ -16,6 +16,7 @@ import Foundation import UIKit +import GBDeviceInfo extension RoomInputToolbarView { open override func sendCurrentMessage() { @@ -28,15 +29,66 @@ extension RoomInputToolbarView { self.becomeFirstResponder() temp.removeFromSuperview() } - + // Send message if any. if let messageToSend = self.attributedTextMessage, messageToSend.length > 0 { self.delegate.roomInputToolbarView(self, sendAttributedTextMessage: messageToSend) } - + // Reset message, disable view animation during the update to prevent placeholder distorsion. UIView.setAnimationsEnabled(false) self.attributedTextMessage = nil UIView.setAnimationsEnabled(true) } } + +@objc extension RoomInputToolbarView { + func updatePlaceholder() { + updatePlaceholderText() + } +} + +extension RoomInputToolbarViewProtocol where Self: MXKRoomInputToolbarView { + func updatePlaceholderText() { + // Consider the default placeholder + + let placeholder: String + + // Check the device screen size before using large placeholder + let shouldDisplayLargePlaceholder = GBDeviceInfo.deviceInfo().family == .familyiPad || GBDeviceInfo.deviceInfo().displayInfo.display.rawValue >= GBDeviceDisplay.display5p8Inch.rawValue + + if !shouldDisplayLargePlaceholder { + switch sendMode { + case .reply: + placeholder = VectorL10n.roomMessageReplyToShortPlaceholder + case .createDM: + placeholder = VectorL10n.roomFirstMessagePlaceholder + + default: + placeholder = VectorL10n.roomMessageShortPlaceholder + } + } else { + if isEncryptionEnabled { + switch sendMode { + case .reply: + placeholder = VectorL10n.encryptedRoomMessageReplyToPlaceholder + + default: + placeholder = VectorL10n.encryptedRoomMessagePlaceholder + } + } else { + switch sendMode { + case .reply: + placeholder = VectorL10n.roomMessageReplyToPlaceholder + + case .createDM: + placeholder = VectorL10n.roomFirstMessagePlaceholder + default: + placeholder = VectorL10n.roomMessagePlaceholder + } + } + } + + self.placeholder = placeholder + } +} diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index 5f2c06cc7..49f496dbd 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -29,8 +29,6 @@ import CoreGraphics // The toolbar for editing with rich text class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInputToolbarViewProtocol { - - // MARK: - Properties // MARK: Private @@ -41,6 +39,11 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp private var viewModel: ComposerViewModelProtocol = ComposerViewModel(initialViewState: ComposerViewState()) // MARK: Public + var isEncryptionEnabled = false { + didSet { + updatePlaceholderText() + } + } /// The current html content of the composer var htmlContent: String { @@ -69,6 +72,16 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp } set { viewModel.sendMode = ComposerSendMode(from: newValue) + updatePlaceholderText() + } + } + + override var placeholder: String! { + get { + viewModel.placeholder + } + set { + viewModel.placeholder = newValue } } @@ -86,13 +99,11 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp super.awakeFromNib() viewModel.callback = { [weak self] result in - guard let self = self else { return } - switch result { - case .cancel: - self.toolbarViewDelegate?.roomInputToolbarViewDidTapCancel(self) - } + self?.handleViewModelResult(result) } + inputAccessoryViewForKeyboard = UIView(frame: .zero) + let composer = Composer(viewModel: viewModel.context, wysiwygViewModel: wysiwygViewModel, sendMessageAction: { [weak self] content in @@ -150,11 +161,17 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp delegate?.roomInputToolbarView?(self, sendFormattedTextMessage: content.html, withRawText: content.plainText) } - private func showSendMediaActions() { delegate?.roomInputToolbarViewShowSendMediaActions?(self) } + private func handleViewModelResult(_ result: ComposerViewModelResult) { + switch result { + case .cancel: + self.toolbarViewDelegate?.roomInputToolbarViewDidTapCancel(self) + } + } + private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) } diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift index 18da157e5..46e06cfed 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift @@ -41,6 +41,7 @@ class VoiceMessageAudioPlayer: NSObject { private var playbackBufferEmptyObserver: NSKeyValueObservation? private var rateObserver: NSKeyValueObservation? private var playToEndObserver: NSObjectProtocol? + private var appBackgroundObserver: NSObjectProtocol? private let delegateContainer = DelegateContainer() @@ -225,6 +226,15 @@ class VoiceMessageAudioPlayer: NSObject { (delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidFinishPlaying(self) } } + + appBackgroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in + guard let self = self, !BuildSettings.allowBackgroundAudioMessagePlayback else { return } + + self.pause() + self.delegateContainer.notifyDelegatesWithBlock { delegate in + (delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidPausePlaying(self) + } + } } private func removeObservers() { @@ -232,6 +242,7 @@ class VoiceMessageAudioPlayer: NSObject { playbackBufferEmptyObserver?.invalidate() rateObserver?.invalidate() NotificationCenter.default.removeObserver(playToEndObserver as Any) + NotificationCenter.default.removeObserver(appBackgroundObserver as Any) } } diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift index 3037c67d0..54262f828 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift @@ -183,6 +183,10 @@ import MediaPlayer } private func setUpRemoteCommandCenter() { + guard BuildSettings.allowBackgroundAudioMessagePlayback else { + return + } + displayLink.isPaused = false UIApplication.shared.beginReceivingRemoteControlEvents() @@ -252,14 +256,8 @@ import MediaPlayer return } - let artwork = MPMediaItemArtwork(boundsSize: Constants.roomAvatarImageSize) { [weak self] size in - return self?.roomAvatar ?? UIImage() - } - let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default() - nowPlayingInfoCenter.nowPlayingInfo = [MPMediaItemPropertyTitle: audioPlayer.displayName ?? VectorL10n.voiceMessageLockScreenPlaceholder, - MPMediaItemPropertyArtist: currentRoomSummary?.displayname as Any, - MPMediaItemPropertyArtwork: artwork, + nowPlayingInfoCenter.nowPlayingInfo = [MPMediaItemPropertyTitle: VectorL10n.voiceMessageLockScreenPlaceholder, MPMediaItemPropertyPlaybackDuration: audioPlayer.duration as Any, MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime as Any] } diff --git a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCellVM.swift b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCellVM.swift index 099102014..27659b9be 100644 --- a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCellVM.swift +++ b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCellVM.swift @@ -27,19 +27,7 @@ struct DirectoryRoomTableViewCellVM { // TODO: Use AvatarView subclass in the cell view func setAvatar(in avatarImageView: MXKImageView) { - - let defaultAvatarImage: UIImage? - var defaultAvatarImageContentMode: UIView.ContentMode = .scaleAspectFill - - switch self.avatarViewData.fallbackImage { - case .matrixItem(let matrixItemId, let matrixItemDisplayName): - defaultAvatarImage = AvatarGenerator.generateAvatar(forMatrixItem: matrixItemId, withDisplayName: matrixItemDisplayName) - case .image(let image, let contentMode): - defaultAvatarImage = image - defaultAvatarImageContentMode = contentMode ?? .scaleAspectFill - case .none: - defaultAvatarImage = nil - } + let (defaultAvatarImage, defaultAvatarImageContentMode) = avatarViewData.fallbackImageParameters() ?? (nil, .scaleAspectFill) if let avatarUrl = self.avatarViewData.avatarUrl { avatarImageView.enableInMemoryCache = true diff --git a/Riot/Modules/User/Avatar/UserAvatarViewData.swift b/Riot/Modules/User/Avatar/UserAvatarViewData.swift index 2f9dea0f0..2dad83e98 100644 --- a/Riot/Modules/User/Avatar/UserAvatarViewData.swift +++ b/Riot/Modules/User/Avatar/UserAvatarViewData.swift @@ -26,7 +26,7 @@ struct UserAvatarViewData: AvatarViewDataProtocol { return userId } - var fallbackImage: AvatarFallbackImage? { - return .matrixItem(matrixItemId, displayName) + var fallbackImages: [AvatarFallbackImage]? { + [.matrixItem(matrixItemId, displayName), .image(Asset.Images.tabPeople.image, .scaleAspectFill)] } } diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift index 6cba03bed..0f8ad1fdc 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift @@ -19,6 +19,7 @@ import Foundation struct ComposerViewState: BindableState { var eventSenderDisplayName: String? var sendMode: ComposerSendMode = .send + var placeholder: String? } extension ComposerViewState { diff --git a/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift b/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift index f125b638a..5f16cfa42 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift @@ -63,4 +63,10 @@ final class ComposerViewModelTests: XCTestCase { context.send(viewAction: .cancel) XCTAssert(result == .cancel) } + + func testPlaceholder() { + XCTAssert(context.viewState.placeholder == nil) + viewModel.placeholder = "Placeholder Test" + XCTAssert(context.viewState.placeholder == "Placeholder Test") + } } diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index 64069e5c3..0bcfd6ffd 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -111,6 +111,7 @@ struct Composer: View { didUpdateText: wysiwygViewModel.didUpdateText ) .tintColor(theme.colors.accent) + .placeholder(viewModel.viewState.placeholder, color: theme.colors.tertiaryContent) .frame(height: wysiwygViewModel.idealHeight) .padding(.horizontal, horizontalPadding) .onAppear { diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift index dcb1ec6fe..65fd747b6 100644 --- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift +++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift @@ -45,6 +45,15 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol } } + var placeholder: String? { + get { + state.placeholder + } + set { + state.placeholder = newValue + } + } + // MARK: - Public override func process(viewAction: ComposerViewAction) { diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift index 1448f2d1b..70d943dc7 100644 --- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift @@ -21,4 +21,5 @@ protocol ComposerViewModelProtocol { var callback: ((ComposerViewModelResult) -> Void)? { get set } var sendMode: ComposerSendMode { get set } var eventSenderDisplayName: String? { get set } + var placeholder: String? { get set } } diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift index a587b23d8..1acd907a4 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift @@ -84,8 +84,7 @@ final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDel func start() { } func toPresentable() -> UIViewController { - VectorHostingController(rootView: TimelinePollView(viewModel: viewModel.context), - forceZeroSafeAreaInsets: true) + VectorHostingController(rootView: TimelinePollView(viewModel: viewModel.context)) } func canEndPoll() -> Bool { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift index 78b1d8ab7..31fb63849 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift @@ -26,13 +26,13 @@ class TimelinePollProvider { /// Create or retrieve the poll timeline coordinator for this event and return /// a view to be displayed in the timeline - func buildTimelinePollViewForEvent(_ event: MXEvent) -> UIView? { + func buildTimelinePollVCForEvent(_ event: MXEvent) -> UIViewController? { guard let session = session, let room = session.room(withRoomId: event.roomId) else { return nil } if let coordinator = coordinatorsForEventIdentifiers[event.eventId] { - return coordinator.toPresentable().view + return coordinator.toPresentable() } let parameters = TimelinePollCoordinatorParameters(session: session, room: room, pollStartEvent: event) @@ -42,7 +42,7 @@ class TimelinePollProvider { coordinatorsForEventIdentifiers[event.eventId] = coordinator - return coordinator.toPresentable().view + return coordinator.toPresentable() } /// Retrieve the poll timeline coordinator for the given event or nil if it hasn't been created yet diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift index fcaf0ceb2..4184f0d63 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift @@ -61,8 +61,7 @@ final class VoiceBroadcastPlaybackCoordinator: Coordinator, Presentable { func start() { } func toPresentable() -> UIViewController { - VectorHostingController(rootView: VoiceBroadcastPlaybackView(viewModel: viewModel.context), - forceZeroSafeAreaInsets: true) + VectorHostingController(rootView: VoiceBroadcastPlaybackView(viewModel: viewModel.context)) } func canEndVoiceBroadcast() -> Bool { diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift index d13a9a77d..5167a2364 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift @@ -26,13 +26,13 @@ class VoiceBroadcastPlaybackProvider { /// Create or retrieve the voiceBroadcast timeline coordinator for this event and return /// a view to be displayed in the timeline - func buildVoiceBroadcastPlaybackViewForEvent(_ event: MXEvent, senderDisplayName: String?) -> UIView? { + func buildVoiceBroadcastPlaybackVCForEvent(_ event: MXEvent, senderDisplayName: String?) -> UIViewController? { guard let session = session, let room = session.room(withRoomId: event.roomId) else { return nil } if let coordinator = coordinatorsForEventIdentifiers[event.eventId] { - return coordinator.toPresentable().view + return coordinator.toPresentable() } let dispatchGroup = DispatchGroup() @@ -62,7 +62,7 @@ class VoiceBroadcastPlaybackProvider { coordinatorsForEventIdentifiers[event.eventId] = coordinator - return coordinator.toPresentable().view + return coordinator.toPresentable() } diff --git a/changelog.d/5326.bugfix b/changelog.d/5326.bugfix new file mode 100644 index 000000000..8eaa35254 --- /dev/null +++ b/changelog.d/5326.bugfix @@ -0,0 +1 @@ +Timeline: Fix layout for SwiftUI content views. diff --git a/changelog.d/6847.bugfix b/changelog.d/6847.bugfix new file mode 100644 index 000000000..3e8dcd7a1 --- /dev/null +++ b/changelog.d/6847.bugfix @@ -0,0 +1 @@ +Updates the avatar image loading logics. diff --git a/changelog.d/6935.change b/changelog.d/6935.change new file mode 100644 index 000000000..43807527e --- /dev/null +++ b/changelog.d/6935.change @@ -0,0 +1 @@ +Added a responsive placeholder text to the Rich Text Composer diff --git a/changelog.d/pr-6870.feature b/changelog.d/pr-6870.feature new file mode 100644 index 000000000..2a4ba4edc --- /dev/null +++ b/changelog.d/pr-6870.feature @@ -0,0 +1 @@ +Changed the info in the background audio message player. diff --git a/changelog.d/pr-6936.change b/changelog.d/pr-6936.change new file mode 100644 index 000000000..d1e649c9f --- /dev/null +++ b/changelog.d/pr-6936.change @@ -0,0 +1 @@ +Improves external links interaction UX. diff --git a/project.yml b/project.yml index 722cce972..391e91acc 100644 --- a/project.yml +++ b/project.yml @@ -53,7 +53,7 @@ packages: branch: main WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - revision: 11dad16e3e589dba423f6cc5707e9df8aace89b0 + revision: d5ef7054fb43924d5b92d5d627347ca2bc333717 DeviceKit: url: https://github.com/devicekit/DeviceKit majorVersion: 4.7.0