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/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 2bf4c421b..3b6c01171 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; } @@ -5026,12 +5026,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 +5040,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/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/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/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/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