Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vector-im/element-ios/issues/4976 - Replaced GrowingTextView with sim… #5052

Merged
merged 2 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ abstract_target 'RiotPods' do
pod 'SideMenu', '~> 6.5'
pod 'DSWaveformImage', '~> 6.1.1'
pod 'ffmpeg-kit-ios-audio', '~> 4.5'
pod 'GrowingTextView', '~> 0.7.2'

pod 'FLEX', '~> 4.5.0', :configurations => ['Debug']

Expand Down
124 changes: 115 additions & 9 deletions Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,100 @@
// limitations under the License.
//

import GrowingTextView

@objc protocol RoomInputToolbarTextViewDelegate: AnyObject {
func textView(_ textView: RoomInputToolbarTextView, didChangeHeight height: CGFloat)
func textView(_ textView: RoomInputToolbarTextView, didReceivePasteForMediaFromSender sender: Any?)
}

class RoomInputToolbarTextView: GrowingTextView {
@objcMembers
class RoomInputToolbarTextView: UITextView {

@objc weak var toolbarDelegate: RoomInputToolbarTextViewDelegate?
private var heightConstraint: NSLayoutConstraint!

weak var toolbarDelegate: RoomInputToolbarTextViewDelegate?

var placeholder: String? {
didSet {
setNeedsDisplay()
}
}

override var keyCommands: [UIKeyCommand]? {
return [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(keyCommandSelector(_:)))]
var placeholderColor: UIColor = UIColor(white: 0.8, alpha: 1.0) {
didSet {
setNeedsDisplay()
}
}

@objc private func keyCommandSelector(_ keyCommand: UIKeyCommand) {
guard keyCommand.input == "\r", let delegate = (self.delegate as? RoomInputToolbarView) else {
var minHeight: CGFloat = 30.0 {
didSet {
updateUI()
}
}

var maxHeight: CGFloat = 0.0 {
didSet {
updateUI()
}
}

override var text: String! {
didSet {
updateUI()
}
}

override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}

private func commonInit() {
contentMode = .redraw

NotificationCenter.default.addObserver(self, selector: #selector(textDidChange), name: UITextView.textDidChangeNotification, object: self)

if let heightConstraint = constraints.filter({ $0.firstAttribute == .height && $0.relation == .equal }).first {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this potentially break if someone added an equal heights constraint between the text view and something else in the future?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break might a a strong word. If there's another height constraint set and the textView detects it then it will set its height on that.
If there's 2 of them active at the same time for some reason you will either see breaking constraints or it will just work if the priorities are different.
Either way, it breaking shouldn't be a particularly hard problem to find or fix.

self.heightConstraint = heightConstraint
} else {
heightConstraint = self.heightAnchor.constraint(equalToConstant: minHeight)
addConstraint(heightConstraint)
}
}

// MARK: - Overrides

override func layoutSubviews() {
super.layoutSubviews()
updateUI()
}

override func draw(_ rect: CGRect) {
super.draw(rect)

guard text.isEmpty, let placeholder = placeholder else {
return
}

delegate.onTouchUp(inside: delegate.rightInputToolbarButton)
var attributes: [NSAttributedString.Key: Any] = [.foregroundColor: placeholderColor]
if let font = font {
attributes[.font] = font
}

let frame = rect.inset(by: .init(top: textContainerInset.top,
left: textContainerInset.left + textContainer.lineFragmentPadding,
bottom: textContainerInset.bottom,
right: textContainerInset.right))

placeholder.draw(in: frame, withAttributes: attributes)
}

override var keyCommands: [UIKeyCommand]? {
return [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(keyCommandSelector(_:)))]
}

/// Overrides paste to handle images pasted from Safari, passing them up to the input toolbar.
Expand All @@ -49,4 +123,36 @@ class RoomInputToolbarTextView: GrowingTextView {
super.paste(sender)
}
}

// MARK: - Private

@objc private func textDidChange(notification: Notification) {
if let sender = notification.object as? RoomInputToolbarTextView, sender == self {
updateUI()
}
}

private func updateUI() {
var height = sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude)).height
height = minHeight > 0 ? max(height, minHeight) : height
height = maxHeight > 0 ? min(height, maxHeight) : height

// Update placeholder
self.setNeedsDisplay()

guard height != heightConstraint.constant else {
return
}

heightConstraint.constant = height
toolbarDelegate?.textView(self, didChangeHeight: height)
}

@objc private func keyCommandSelector(_ keyCommand: UIKeyCommand) {
guard keyCommand.input == "\r", let delegate = (self.delegate as? RoomInputToolbarView) else {
return
}

delegate.onTouchUp(inside: delegate.rightInputToolbarButton)
}
}
44 changes: 23 additions & 21 deletions Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,10 @@ typedef enum : NSUInteger
*/
@property (nonatomic, weak) id<RoomInputToolbarViewDelegate> delegate;

@property (weak, nonatomic) IBOutlet UIView *mainToolbarView;

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint;

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint;

@property (weak, nonatomic) IBOutlet UIButton *attachMediaButton;

@property (weak, nonatomic) IBOutlet UIImageView *inputTextBackgroundView;

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputContextViewHeightConstraint;
@property (weak, nonatomic) IBOutlet UIImageView *inputContextImageView;
@property (weak, nonatomic) IBOutlet UILabel *inputContextLabel;
@property (weak, nonatomic) IBOutlet UIButton *inputContextButton;
@property (weak, nonatomic) IBOutlet RoomActionsBar *actionsBar;
@property (weak, nonatomic) UIView *voiceMessageToolbarView;

/**
Tell whether the filled data will be sent encrypted. NO by default.
*/
@property (nonatomic) BOOL isEncryptionEnabled;
@property (nonatomic, assign) BOOL isEncryptionEnabled;

/**
Sender of the event being edited / replied.
Expand All @@ -91,11 +73,31 @@ typedef enum : NSUInteger
/**
Destination of the message in the composer.
*/
@property (nonatomic) RoomInputToolbarViewSendMode sendMode;
@property (nonatomic, assign) RoomInputToolbarViewSendMode sendMode;

/**
YES if action menu is opened. NO otherwise
*/
@property (nonatomic, getter=isActionMenuOpened) BOOL actionMenuOpened;
@property (nonatomic, assign) BOOL actionMenuOpened;

/**
The input toolbar's main height constraint
*/
@property (nonatomic, weak, readonly) NSLayoutConstraint *mainToolbarHeightConstraint;

/**
The input toolbar's action bar
*/
@property (nonatomic, weak, readonly) RoomActionsBar *actionsBar;

/**
The attach media button
*/
@property (nonatomic, weak, readonly) UIButton *attachMediaButton;

/**
Adds a voice message toolbar view to be displayed inside this input toolbar
*/
- (void)setVoiceMessageToolbarView:(UIView *)toolbarView;

@end
98 changes: 42 additions & 56 deletions Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,51 @@

#import "RoomInputToolbarView.h"

#import "ThemeService.h"
#import "Riot-Swift.h"

#import "GBDeviceInfo_iOS.h"

#import "UINavigationController+Riot.h"
static const CGFloat kContextBarHeight = 24;
static const CGFloat kActionMenuAttachButtonSpringVelocity = 7;
static const CGFloat kActionMenuAttachButtonSpringDamping = .45;

#import "WidgetManager.h"
#import "IntegrationManagerViewController.h"
static const NSTimeInterval kSendModeAnimationDuration = .15;
static const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4;
static const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2;
static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;

@import GrowingTextView;
@interface RoomInputToolbarView() <UITextViewDelegate, RoomInputToolbarTextViewDelegate>

const double kContextBarHeight = 24;
const NSTimeInterval kSendModeAnimationDuration = .15;
const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4;
const CGFloat kActionMenuAttachButtonSpringVelocity = 7;
const CGFloat kActionMenuAttachButtonSpringDamping = .45;
const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2;
const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
const CGFloat kComposerContainerTrailingPadding = 12;
@property (nonatomic, weak) IBOutlet UIView *mainToolbarView;

@interface RoomInputToolbarView() <GrowingTextViewDelegate, RoomInputToolbarTextViewDelegate>
{
// The intermediate action sheet
UIAlertController *actionSheet;
}
@property (nonatomic, weak) IBOutlet UIButton *attachMediaButton;

@property (nonatomic, weak) IBOutlet RoomInputToolbarTextView *textView;
@property (nonatomic, weak) IBOutlet UIImageView *inputTextBackgroundView;

@property (nonatomic, weak) IBOutlet UIImageView *inputContextImageView;
@property (nonatomic, weak) IBOutlet UILabel *inputContextLabel;
@property (nonatomic, weak) IBOutlet UIButton *inputContextButton;

@property (nonatomic, weak) IBOutlet RoomActionsBar *actionsBar;

@property (nonatomic, weak) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint;
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint;
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint;
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *inputContextViewHeightConstraint;

@property (nonatomic, weak) UIView *voiceMessageToolbarView;

@property (nonatomic, assign) CGFloat expandedMainToolbarHeight;

@end

@implementation RoomInputToolbarView
@dynamic delegate;

+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([RoomInputToolbarView class])
bundle:[NSBundle bundleForClass:[RoomInputToolbarView class]]];
}

+ (instancetype)roomInputToolbarView
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
{
if ([[self class] nib])
{
return [[[self class] nib] instantiateWithOwner:nil options:nil].firstObject;
}
else
{
return [[self alloc] init];
}
UINib *nib = [UINib nibWithNibName:NSStringFromClass([RoomInputToolbarView class]) bundle:nil];
return [nib instantiateWithOwner:nil options:nil].firstObject;
}

- (void)awakeFromNib
Expand Down Expand Up @@ -315,6 +309,11 @@ - (void)setPlaceholder:(NSString *)inPlaceholder
self.textView.placeholder = inPlaceholder;
}

- (void)pasteText:(NSString *)text
{
self.textMessage = [self.textView.text stringByReplacingCharactersInRange:self.textView.selectedRange withString:text];
}

#pragma mark - Actions

- (IBAction)cancelAction:(id)sender
Expand All @@ -325,7 +324,7 @@ - (IBAction)cancelAction:(id)sender
}
}

#pragma mark - GrowingTextViewDelegate
#pragma mark - UITextViewDelegate

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
Expand All @@ -351,7 +350,9 @@ - (void)textViewDidChange:(UITextView *)textView
[self.delegate roomInputToolbarViewDidChangeTextMessage:self];
}

- (void)textViewDidChangeHeight:(GrowingTextView *)textView height:(CGFloat)height
#pragma mark - RoomInputToolbarTextViewDelegate

- (void)textView:(RoomInputToolbarTextView *)textView didChangeHeight:(CGFloat)height
{
// Update height of the main toolbar (message composer)
CGFloat updatedHeight = height + (self.messageComposerContainerTopConstraint.constant + self.messageComposerContainerBottomConstraint.constant) + self.inputContextViewHeightConstraint.constant;
Expand All @@ -376,13 +377,18 @@ - (void)textViewDidChangeHeight:(GrowingTextView *)textView height:(CGFloat)heig
}
}

- (void)textView:(RoomInputToolbarTextView *)textView didReceivePasteForMediaFromSender:(id)sender
{
[self paste:sender];
}

#pragma mark - Override MXKRoomInputToolbarView

- (IBAction)onTouchUpInside:(UIButton*)button
{
if (button == self.attachMediaButton)
{
self.actionMenuOpened = !self.isActionMenuOpened;
self.actionMenuOpened = !self.actionMenuOpened;
}

[super onTouchUpInside:button];
Expand All @@ -400,12 +406,6 @@ - (void)dismissKeyboard

- (void)destroy
{
if (actionSheet)
{
[actionSheet dismissViewControllerAnimated:NO completion:nil];
actionSheet = nil;
}

[super destroy];
}

Expand Down Expand Up @@ -462,20 +462,6 @@ - (void)setActionMenuOpened:(BOOL)actionMenuOpened
}
}

#pragma mark - Clipboard - Handle image/data paste from general pasteboard

- (void)paste:(id)sender
{
// TODO Custom here the validation screen for each available item

[super paste:sender];
}

- (void)textView:(GrowingTextView *)textView didReceivePasteForMediaFromSender:(id)sender
{
[self paste:sender];
}

#pragma mark - Private

- (void)updateUIWithTextMessage:(NSString *)textMessage animated:(BOOL)animated
Expand Down
1 change: 1 addition & 0 deletions changelog.d/4976.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Replaced GrowingTextView with simpler, custom implementation. Cleaned up the RoomInputToolbar header.