Merge pull request #7059 from vector-im/mauroromito/fullscreen_mode_2

Rich Text Editor: Fullscreen mode
This commit is contained in:
Velin92 2022-11-15 21:18:51 +01:00 committed by GitHub
commit b5dc19d930
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 274 additions and 15 deletions

View file

@ -47,6 +47,7 @@ typedef enum : NSUInteger
@class MXKRoomInputToolbarView;
@class MXKImageView;
@protocol MXKRoomInputToolbarViewDelegate <NSObject>
/**
@ -381,4 +382,6 @@ typedef enum : NSUInteger
*/
@property (nonatomic) NSAttributedString *attributedTextMessage;
- (void)dismissValidationView:(MXKImageView*)validationView;
@end

View file

@ -214,14 +214,14 @@ typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) {
@property (weak, nonatomic) IBOutlet UITableView *bubblesTableView;
@property (weak, nonatomic) IBOutlet UIView *roomTitleViewContainer;
@property (weak, nonatomic) IBOutlet UIView *roomInputToolbarContainer;
@property (strong, nonatomic) IBOutlet UIView *roomInputToolbarContainer;
@property (weak, nonatomic) IBOutlet UIView *roomActivitiesContainer;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bubblesTableViewTopConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bubblesTableViewBottomConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomActivitiesContainerHeightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomInputToolbarContainerHeightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomInputToolbarContainerBottomConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *roomInputToolbarContainerBottomConstraint;
#pragma mark - Class methods

View file

@ -72,6 +72,7 @@ extern NSTimeInterval const kResizeComposerAnimationDuration;
@property (weak, nonatomic, nullable) IBOutlet UIView *inputBackgroundView;
@property (weak, nonatomic, nullable) IBOutlet UIButton *scrollToBottomButton;
@property (weak, nonatomic, nullable) IBOutlet BadgeLabel *scrollToBottomBadgeLabel;
@property (nonatomic, strong) IBOutlet UIView *overlayContainerView;
// Remove Jitsi widget container
@property (weak, nonatomic, nullable) IBOutlet UIView *removeJitsiWidgetContainer;
@ -115,6 +116,11 @@ extern NSTimeInterval const kResizeComposerAnimationDuration;
// The voice broadcast service
@property (nonatomic, nullable) VoiceBroadcastService *voiceBroadcastService;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray<NSLayoutConstraint*> *toolbarContainerConstraints;
@property (strong, nonatomic, nullable) UIView* maximisedToolbarDimmingView;
/**
Retrieve the live data source in cases where the timeline is not live.

View file

@ -187,7 +187,6 @@ static CGSize kThreadListBarButtonItemImageSize;
MXTaskProfile *notificationTaskProfile;
}
@property (nonatomic, weak) IBOutlet UIView *overlayContainerView;
@property (nonatomic, strong) RemoveJitsiWidgetView *removeJitsiWidgetView;
@ -470,6 +469,9 @@ static CGSize kThreadListBarButtonItemImageSize;
self.jumpToLastUnreadBanner.backgroundColor = ThemeService.shared.theme.colors.navigation;
[self.jumpToLastUnreadBanner vc_removeShadow];
self.resetReadMarkerButton.tintColor = ThemeService.shared.theme.colors.quarterlyContent;
if (self.maximisedToolbarDimmingView) {
self.maximisedToolbarDimmingView.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.29];
}
}
else
{
@ -481,6 +483,9 @@ static CGSize kThreadListBarButtonItemImageSize;
radius:8
opacity:0.1];
self.resetReadMarkerButton.tintColor = ThemeService.shared.theme.colors.tertiaryContent;
if (self.maximisedToolbarDimmingView) {
self.maximisedToolbarDimmingView.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.12];
}
}
self.scrollToBottomBadgeLabel.badgeColor = ThemeService.shared.theme.tintColor;
@ -603,6 +608,8 @@ static CGSize kThreadListBarButtonItemImageSize;
// Stop the loading indicator even if the session is still in progress
[self stopLoadingUserIndicator];
[self setMaximisedToolbarIsHiddenIfNeeded: YES];
}
- (void)viewDidAppear:(BOOL)animated
@ -678,6 +685,8 @@ static CGSize kThreadListBarButtonItemImageSize;
// Note: We have to wait for viewDidAppear before updating growingTextView (viewWillAppear is too early)
self.inputToolbarView.attributedTextMessage = self.roomDataSource.partialAttributedTextMessage;
}
[self setMaximisedToolbarIsHiddenIfNeeded: NO];
}
- (void)viewDidDisappear:(BOOL)animated
@ -1212,8 +1221,6 @@ static CGSize kThreadListBarButtonItemImageSize;
if (!self.inputToolbarView || ![self.inputToolbarView isMemberOfClass:roomInputToolbarViewClass])
{
[super setRoomInputToolbarViewClass:roomInputToolbarViewClass];
if ([self.inputToolbarView.class conformsToProtocol:@protocol(RoomInputToolbarViewProtocol)]) {
id<RoomInputToolbarViewProtocol> inputToolbar = (id<RoomInputToolbarViewProtocol>)self.inputToolbarView;
[inputToolbar setVoiceMessageToolbarView:self.voiceMessageController.voiceMessageToolbarView];

View file

@ -154,6 +154,85 @@ extension RoomViewController {
RiotSettings.shared.enableWysiwygTextFormatting.toggle()
wysiwygInputToolbar?.textFormattingEnabled.toggle()
}
@objc func didChangeMaximisedState(_ isMaximised: Bool) {
guard let wysiwygInputToolbar = wysiwygInputToolbar else { return }
if isMaximised {
var view: UIView!
// iPhone
if let navView = self.navigationController?.navigationController?.view {
view = navView
// iPad
} else if let navView = self.navigationController?.view {
view = navView
} else {
return
}
var originalRect = roomInputToolbarContainer.convert(roomInputToolbarContainer.frame, to: view)
var textView: UITextView?
if wysiwygInputToolbar.isFocused {
textView = UITextView()
self.view.window?.addSubview(textView!)
textView?.becomeFirstResponder()
originalRect = wysiwygInputToolbar.convert(wysiwygInputToolbar.frame, to: view)
}
wysiwygInputToolbar.showKeyboard()
roomInputToolbarContainer.removeFromSuperview()
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
// Same as the system dimming background color
dimmingView.backgroundColor = .black.withAlphaComponent(ThemeService.shared().isCurrentThemeDark() ? 0.29 : 0.12)
maximisedToolbarDimmingView = dimmingView
view.addSubview(dimmingView)
dimmingView.frame = view.bounds
NSLayoutConstraint.activate(
[
dimmingView.topAnchor.constraint(equalTo: view.topAnchor),
dimmingView.leftAnchor.constraint(equalTo: view.leftAnchor),
dimmingView.rightAnchor.constraint(equalTo: view.rightAnchor),
dimmingView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
]
)
dimmingView.addSubview(self.roomInputToolbarContainer)
roomInputToolbarContainer.frame = originalRect
roomInputToolbarContainer.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
roomInputToolbarContainer.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
roomInputToolbarContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
UIView.animate(withDuration: kResizeComposerAnimationDuration, delay: 0, options: [.curveEaseInOut]) {
view.layoutIfNeeded()
}
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPanRoomToolbarContainer(_ :)))
roomInputToolbarContainer.addGestureRecognizer(panGesture)
textView?.removeFromSuperview()
} else {
let originalRect = wysiwygInputToolbar.convert(wysiwygInputToolbar.frame, to: view)
var textView: UITextView?
if wysiwygInputToolbar.isFocused {
textView = UITextView()
self.view.window?.addSubview(textView!)
textView?.becomeFirstResponder()
wysiwygInputToolbar.showKeyboard()
}
self.roomInputToolbarContainer.removeFromSuperview()
maximisedToolbarDimmingView?.removeFromSuperview()
maximisedToolbarDimmingView = nil
self.view.insertSubview(self.roomInputToolbarContainer, belowSubview: self.overlayContainerView)
roomInputToolbarContainer.frame = originalRect
NSLayoutConstraint.activate(self.toolbarContainerConstraints)
self.roomInputToolbarContainerBottomConstraint.isActive = true
UIView.animate(withDuration: kResizeComposerAnimationDuration, delay: 0, options: [.curveEaseInOut]) {
self.view.layoutIfNeeded()
}
roomInputToolbarContainer.gestureRecognizers?.removeAll()
textView?.removeFromSuperview()
}
}
@objc func setMaximisedToolbarIsHiddenIfNeeded(_ isHidden: Bool) {
if wysiwygInputToolbar?.isMaximised == true {
roomInputToolbarContainer.superview?.isHidden = isHidden
}
}
}
// MARK: - Private Helpers
@ -165,4 +244,28 @@ private extension RoomViewController {
var wysiwygInputToolbar: WysiwygInputToolbarView? {
return self.inputToolbarView as? WysiwygInputToolbarView
}
@objc private func didPanRoomToolbarContainer(_ sender: UIPanGestureRecognizer) {
guard let wysiwygInputToolbar = wysiwygInputToolbar else { return }
switch sender.state {
case .began:
break
case .changed:
let translation = sender.translation(in: view)
let translatedValue = wysiwygInputToolbar.maxExpandedHeight - translation.y
guard translatedValue <= wysiwygInputToolbar.maxExpandedHeight, translatedValue >= wysiwygInputToolbar.compressedHeight else { return }
wysiwygInputToolbar.idealHeight = translatedValue
case .ended:
if wysiwygInputToolbar.idealHeight <= wysiwygInputToolbar.maxCompressedHeight {
wysiwygInputToolbar.minimise()
} else {
wysiwygInputToolbar.idealHeight = wysiwygInputToolbar.maxExpandedHeight
}
case .cancelled:
wysiwygInputToolbar.idealHeight = wysiwygInputToolbar.maxExpandedHeight
default:
break
}
}
}

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -35,6 +35,11 @@
<outlet property="userSuggestionContainerHeightConstraint" destination="1Cd-cT-gOr" id="au5-3q-r54"/>
<outlet property="userSuggestionContainerView" destination="oni-F4-X1U" id="0js-Ji-8Mm"/>
<outlet property="view" destination="iN0-l3-epB" id="ieV-u7-rXU"/>
<outletCollection property="toolbarContainerConstraints" destination="T1Y-r9-bYV" id="wax-9P-KGn"/>
<outletCollection property="toolbarContainerConstraints" destination="ave-fu-X1D" id="gBl-7F-srT"/>
<outletCollection property="toolbarContainerConstraints" destination="pRw-S0-6WL" id="q4S-0g-sqQ"/>
<outletCollection property="toolbarContainerConstraints" destination="QO8-nF-xys" id="aQe-20-4Pq"/>
<outletCollection property="toolbarContainerConstraints" destination="acJ-g8-R7x" id="uEo-Ez-seV"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>

View file

@ -75,6 +75,8 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode)
*/
- (void)roomInputToolbarView:(RoomInputToolbarView *)toolbarView sendAttributedTextMessage:(NSAttributedString *)attributedTextMessage;
- (void)didChangeMaximisedState: (BOOL) isMaximised;
@end
/**

View file

@ -32,9 +32,15 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
// MARK: - Properties
// MARK: Private
private var keyboardHeight: CGFloat = .zero {
didSet {
updateTextViewHeight()
}
}
private var voiceMessageToolbarView: VoiceMessageToolbarView?
private var cancellables = Set<AnyCancellable>()
private var heightConstraint: NSLayoutConstraint!
private var voiceMessageBottomConstraint: NSLayoutConstraint?
private var hostingViewController: VectorHostingController!
private var wysiwygViewModel = WysiwygComposerViewModel(textColor: ThemeService.shared().theme.colors.primaryContent)
private var viewModel: ComposerViewModelProtocol = ComposerViewModel(
@ -52,6 +58,35 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
}
}
override var isFocused: Bool {
viewModel.isFocused
}
var isMaximised: Bool {
wysiwygViewModel.maximised
}
var idealHeight: CGFloat {
get {
wysiwygViewModel.idealHeight
}
set {
wysiwygViewModel.idealHeight = newValue
}
}
var compressedHeight: CGFloat {
wysiwygViewModel.compressedHeight
}
var maxExpandedHeight: CGFloat {
wysiwygViewModel.maxExpandedHeight
}
var maxCompressedHeight: CGFloat {
wysiwygViewModel.maxCompressedHeight
}
// MARK: - Setup
override class func instantiate() -> MXKRoomInputToolbarView! {
@ -115,11 +150,32 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
.removeDuplicates()
.sink { [weak hostingViewController] _ in
hostingViewController?.view.setNeedsLayout()
},
wysiwygViewModel.$maximised
.removeDuplicates()
.sink { [weak self] value in
guard let self = self else { return }
self.toolbarViewDelegate?.didChangeMaximisedState(value)
self.hostingViewController.view.layer.cornerRadius = value ? 20 : 0
}
]
update(theme: ThemeService.shared().theme)
registerThemeServiceDidChangeThemeNotification()
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: UIDevice.orientationDidChangeNotification, object: nil)
}
override func customizeRendering() {
@ -131,8 +187,53 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
self.viewModel.dismissKeyboard()
}
override func dismissValidationView(_ validationView: MXKImageView!) {
super.dismissValidationView(validationView)
if isMaximised {
showKeyboard()
}
}
func showKeyboard() {
self.viewModel.showKeyboard()
}
func minimise() {
wysiwygViewModel.maximised = false
}
// MARK: - Private
@objc private func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
keyboardHeight = keyboardRectangle.height
UIView.performWithoutAnimation {
if self.isMaximised {
self.voiceMessageBottomConstraint?.constant = keyboardHeight - (window?.safeAreaInsets.bottom ?? 0) + 4
} else {
self.voiceMessageBottomConstraint?.constant = 4
}
self.layoutIfNeeded()
}
}
}
@objc private func keyboardWillHide(_ notification: Notification) {
if self.isMaximised {
UIView.performWithoutAnimation {
self.voiceMessageBottomConstraint?.constant = 4
self.layoutIfNeeded()
}
}
}
@objc private func deviceDidRotate(_ notification: Notification) {
DispatchQueue.main.async {
self.updateTextViewHeight()
}
}
private func updateToolbarHeight(wysiwygHeight: CGFloat) {
self.heightConstraint.constant = wysiwygHeight
toolbarViewDelegate?.roomInputToolbarView?(self, heightDidChanged: wysiwygHeight, completion: nil)
@ -140,6 +241,9 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
private func sendWysiwygMessage(content: WysiwygComposerContent) {
delegate?.roomInputToolbarView?(self, sendFormattedTextMessage: content.html, withRawText: content.markdown)
if isMaximised {
minimise()
}
}
private func showSendMediaActions() {
@ -179,6 +283,14 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
wysiwygViewModel.textColor = theme.colors.primaryContent
}
private func updateTextViewHeight() {
let height = UIScreen.main.bounds.height
let barOffset: CGFloat = 68
let toolbarHeight: CGFloat = 82
let finalHeight = height - keyboardHeight - toolbarHeight - barOffset
wysiwygViewModel.maxExpandedHeight = finalHeight
}
// MARK: - HtmlRoomInputToolbarViewProtocol
var isEncryptionEnabled = false {
didSet {
@ -239,17 +351,21 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.deactivate(voiceMessageToolbarView.containersTopConstraints)
addSubview(voiceMessageToolbarView)
let bottomConstraint = hostingViewController.view.bottomAnchor.constraint(equalTo: voiceMessageToolbarView.bottomAnchor, constant: 4)
voiceMessageBottomConstraint = bottomConstraint
NSLayoutConstraint.activate(
[
hostingViewController.view.topAnchor.constraint(equalTo: voiceMessageToolbarView.topAnchor),
hostingViewController.view.leftAnchor.constraint(equalTo: voiceMessageToolbarView.leftAnchor),
hostingViewController.view.bottomAnchor.constraint(equalTo: voiceMessageToolbarView.bottomAnchor, constant: 4),
hostingViewController.view.rightAnchor.constraint(equalTo: voiceMessageToolbarView.rightAnchor)
hostingViewController.view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: voiceMessageToolbarView.topAnchor),
hostingViewController.view.safeAreaLayoutGuide.leftAnchor.constraint(equalTo: voiceMessageToolbarView.leftAnchor),
bottomConstraint,
hostingViewController.view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: voiceMessageToolbarView.rightAnchor)
]
)
} else {
self.voiceMessageToolbarView?.removeFromSuperview()
self.voiceMessageToolbarView = nil
self.voiceMessageBottomConstraint?.isActive = false
self.voiceMessageBottomConstraint = nil
}
}

View file

@ -36,7 +36,7 @@ struct Composer: View {
private let horizontalPadding: CGFloat = 12
private let borderHeight: CGFloat = 40
private var minTextViewHeight: CGFloat = 22
private let minTextViewHeight: CGFloat = 22
private var verticalPadding: CGFloat {
(borderHeight - minTextViewHeight) / 2
}
@ -78,7 +78,7 @@ struct Composer: View {
)
}
}
private var composerContainer: some View {
let rect = RoundedRectangle(cornerRadius: cornerRadius)
return VStack(spacing: 12) {
@ -147,7 +147,7 @@ struct Composer: View {
}
}
}
private var sendMediaButton: some View {
return Button {
showSendMediaActions()
@ -162,7 +162,7 @@ struct Composer: View {
.padding(.trailing, 8)
.accessibilityLabel(VectorL10n.create)
}
private var sendButton: some View {
return Button {
sendMessageAction(wysiwygViewModel.content)
@ -204,6 +204,12 @@ struct Composer: View {
var body: some View {
VStack(spacing: 8) {
if wysiwygViewModel.maximised {
RoundedRectangle(cornerRadius: 4)
.fill(theme.colors.quinaryContent)
.frame(width: 36, height: 5)
.padding(.top, 10)
}
HStack(alignment: .bottom, spacing: 0) {
if !viewModel.viewState.textFormattingEnabled {
sendMediaButton

View file

@ -63,6 +63,10 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol
}
}
var isFocused: Bool {
state.bindings.focused
}
// MARK: - Public
override func process(viewAction: ComposerViewAction) {
@ -77,4 +81,8 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol
func dismissKeyboard() {
state.bindings.focused = false
}
func showKeyboard() {
state.bindings.focused = true
}
}

View file

@ -23,6 +23,8 @@ protocol ComposerViewModelProtocol {
var textFormattingEnabled: Bool { get set }
var eventSenderDisplayName: String? { get set }
var placeholder: String? { get set }
var isFocused: Bool { get }
func dismissKeyboard()
func showKeyboard()
}

1
changelog.d/7058.change Normal file
View file

@ -0,0 +1 @@
Rich Text Composer: Fullscreen mode now is matching the design requirements.