session-ios/Session/Signal/ConversationView/ConversationInputTextView.m

249 lines
7.4 KiB
Mathematica
Raw Normal View History

2017-10-10 22:13:54 +02:00
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
2017-10-10 22:13:54 +02:00
//
#import "ConversationInputTextView.h"
2019-05-02 23:58:48 +02:00
#import "Session-Swift.h"
2020-11-26 00:37:56 +01:00
#import <SessionUtilitiesKit/NSString+SSK.h>
2020-11-12 00:41:45 +01:00
#import <SignalCoreKit/NSString+OWS.h>
2017-10-10 22:13:54 +02:00
NS_ASSUME_NONNULL_BEGIN
@interface ConversationInputTextView () <UITextViewDelegate>
@property (nonatomic) UILabel *placeholderView;
@property (nonatomic) NSArray<NSLayoutConstraint *> *placeholderConstraints;
@end
#pragma mark -
2017-10-10 22:13:54 +02:00
@implementation ConversationInputTextView
- (instancetype)init
{
self = [super init];
if (self) {
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.delegate = self;
self.backgroundColor = nil;
2017-10-10 22:13:54 +02:00
2019-12-09 06:47:13 +01:00
self.showsHorizontalScrollIndicator = NO;
self.showsVerticalScrollIndicator = NO;
2017-10-10 22:13:54 +02:00
self.scrollEnabled = YES;
self.scrollsToTop = NO;
self.userInteractionEnabled = YES;
2019-12-09 06:47:13 +01:00
self.font = [UIFont systemFontOfSize:LKValues.mediumFontSize];
self.textColor = LKColors.text;
2017-10-10 22:13:54 +02:00
self.textAlignment = NSTextAlignmentNatural;
2019-12-09 06:47:13 +01:00
self.tintColor = LKColors.accent;
2017-10-10 22:13:54 +02:00
self.contentMode = UIViewContentModeRedraw;
self.dataDetectorTypes = UIDataDetectorTypeNone;
self.text = nil;
self.placeholderView = [UILabel new];
2019-12-09 06:47:13 +01:00
self.placeholderView.text = NSLocalizedString(@"Message", @"");
self.placeholderView.textColor = [LKColors.text colorWithAlphaComponent:LKValues.composeViewTextFieldPlaceholderOpacity];
2017-10-18 21:21:17 +02:00
self.placeholderView.userInteractionEnabled = NO;
[self addSubview:self.placeholderView];
// We need to do these steps _after_ placeholderView is configured.
2019-12-09 06:47:13 +01:00
self.font = [UIFont systemFontOfSize:LKValues.mediumFontSize];
CGFloat hMarginLeading = 16.f;
CGFloat hMarginTrailing = 16.f;
self.textContainerInset = UIEdgeInsetsMake(11.f,
CurrentAppContext().isRTL ? hMarginTrailing : hMarginLeading,
2019-12-09 06:47:13 +01:00
11.f,
CurrentAppContext().isRTL ? hMarginLeading : hMarginTrailing);
self.textContainer.lineFragmentPadding = 0;
self.contentInset = UIEdgeInsetsZero;
[self ensurePlaceholderConstraints];
[self updatePlaceholderVisibility];
2017-10-10 22:13:54 +02:00
}
return self;
}
#pragma mark -
- (void)setFont:(UIFont *_Nullable)font
{
[super setFont:font];
self.placeholderView.font = font;
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)isAnimated
{
2018-07-03 07:10:50 +02:00
// When creating new lines, contentOffset is animated, but because because
// we are simultaneously resizing the text view, this can cause the
// text in the textview to be "too high" in the text view.
// Solution is to disable animation for setting content offset.
2018-07-03 18:20:17 +02:00
[super setContentOffset:contentOffset animated:NO];
}
- (void)setContentInset:(UIEdgeInsets)contentInset
{
[super setContentInset:contentInset];
[self ensurePlaceholderConstraints];
}
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
{
[super setTextContainerInset:textContainerInset];
[self ensurePlaceholderConstraints];
}
- (void)ensurePlaceholderConstraints
{
OWSAssertDebug(self.placeholderView);
if (self.placeholderConstraints) {
[NSLayoutConstraint deactivateConstraints:self.placeholderConstraints];
}
// We align the location of our placeholder with the text content of
// this view. The only safe way to do that is by measuring the
// beginning position.
UITextRange *beginningTextRange =
[self textRangeFromPosition:self.beginningOfDocument toPosition:self.beginningOfDocument];
CGRect beginningTextRect = [self firstRectForRange:beginningTextRange];
CGFloat topInset = beginningTextRect.origin.y;
2018-07-03 02:17:45 +02:00
CGFloat leftInset = beginningTextRect.origin.x;
2018-07-03 02:17:45 +02:00
// we use Left instead of Leading, since it's based on the prior CGRect offset
self.placeholderConstraints = @[
2018-07-03 02:17:45 +02:00
[self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:leftInset],
[self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeRight],
[self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topInset],
];
}
- (void)updatePlaceholderVisibility
{
2017-10-24 17:43:42 +02:00
self.placeholderView.hidden = self.text.length > 0;
}
- (void)setText:(NSString *_Nullable)text
{
[super setText:text];
[self updatePlaceholderVisibility];
}
2017-10-10 22:13:54 +02:00
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (BOOL)pasteboardHasPossibleAttachment
{
// We don't want to load/convert images more than once so we
// only do a cursory validation pass at this time.
return ([SignalAttachment pasteboardHasPossibleAttachment] && ![SignalAttachment pasteboardHasText]);
}
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender
{
if (action == @selector(paste:)) {
if ([self pasteboardHasPossibleAttachment]) {
return YES;
}
}
return [super canPerformAction:action withSender:sender];
}
- (void)paste:(nullable id)sender
{
if ([self pasteboardHasPossibleAttachment]) {
SignalAttachment *attachment = [SignalAttachment attachmentFromPasteboard];
// Note: attachment might be nil or have an error at this point; that's fine.
[self.inputTextViewDelegate didPasteAttachment:attachment];
return;
}
[super paste:sender];
}
- (NSString *)trimmedText
{
2017-10-18 20:53:31 +02:00
return [self.text ows_stripped];
2017-10-10 22:13:54 +02:00
}
- (void)setPlaceholderText:(NSString *)placeholderText
{
[self.placeholderView setText:placeholderText];
}
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
OWSAssertDebug(self.inputTextViewDelegate);
OWSAssertDebug(self.textViewToolbarDelegate);
[self updatePlaceholderVisibility];
[self.inputTextViewDelegate textViewDidChange:self];
[self.textViewToolbarDelegate textViewDidChange:self];
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
[self.textViewToolbarDelegate textViewDidChangeSelection:self];
}
#pragma mark - Key Commands
- (nullable NSArray<UIKeyCommand *> *)keyCommands
{
// We're permissive about what modifier key we accept for the "send message" hotkey.
2017-11-01 17:48:07 +01:00
// We accept command-return, option-return.
//
// We don't support control-return because it doesn't work.
//
// We don't support shift-return because it is often used for "newline" in other
// messaging apps.
return @[
2017-11-29 18:31:02 +01:00
[self keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierCommand
action:@selector(modifiedReturnPressed:)
discoverabilityTitle:@"Send Message"],
// "Alternate" is option.
2017-11-29 18:31:02 +01:00
[self keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierAlternate
action:@selector(modifiedReturnPressed:)
discoverabilityTitle:@"Send Message"],
];
}
2017-11-29 18:31:02 +01:00
- (UIKeyCommand *)keyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)modifierFlags
action:(SEL)action
discoverabilityTitle:(NSString *)discoverabilityTitle
{
return [UIKeyCommand keyCommandWithInput:input
modifierFlags:modifierFlags
action:action
discoverabilityTitle:discoverabilityTitle];
2017-11-29 18:31:02 +01:00
}
- (void)modifiedReturnPressed:(UIKeyCommand *)sender
{
OWSLogInfo(@"modifiedReturnPressed: %@", sender.input);
[self.inputTextViewDelegate inputTextViewSendMessagePressed];
}
2017-10-10 22:13:54 +02:00
@end
NS_ASSUME_NONNULL_END