Skip to content
Draft
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
4 changes: 3 additions & 1 deletion ios/EnrichedTextInputView.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#import "InputConfig.h"
#import "InputParser.h"
#import "InputTextView.h"
#import "MediaAttachment.h"
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>

Expand All @@ -11,7 +12,8 @@

NS_ASSUME_NONNULL_BEGIN

@interface EnrichedTextInputView : RCTViewComponentView {
@interface EnrichedTextInputView
: RCTViewComponentView <MediaAttachmentDelegate> {
@public
InputTextView *textView;
@public
Expand Down
96 changes: 70 additions & 26 deletions ios/EnrichedTextInputView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ @implementation EnrichedTextInputView {
UILabel *_placeholderLabel;
UIColor *_placeholderColor;
BOOL _emitFocusBlur;
BOOL _didRunInitialMount;
}

// MARK: - Component utils
Expand Down Expand Up @@ -218,6 +219,31 @@ - (void)setupPlaceholderLabel {
_placeholderLabel.hidden = YES;
}

- (void)mediaAttachmentDidUpdate:(NSTextAttachment *)attachment {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will remove this one when #318 will be merged

NSTextStorage *storage = textView.textStorage;
NSRange fullRange = NSMakeRange(0, storage.length);

__block NSRange foundRange = NSMakeRange(NSNotFound, 0);

[storage enumerateAttribute:NSAttachmentAttributeName
inRange:fullRange
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value == attachment) {
foundRange = range;
*stop = YES;
}
}];

if (foundRange.location == NSNotFound) {
return;
}

[storage edited:NSTextStorageEditedAttributes
range:foundRange
changeInLength:0];
}

// MARK: - Props

- (void)updateProps:(Props::Shared const &)props
Expand Down Expand Up @@ -529,32 +555,38 @@ - (void)updateProps:(Props::Shared const &)props
stylePropChanged = YES;
}

if (stylePropChanged) {
// all the text needs to be rebuilt
// we get the current html using old config, then switch to new config and
// replace text using the html this way, the newest config attributes are
// being used!

// the html needs to be generated using the old config
NSString *currentHtml = [parser
parseToHtmlFromRange:NSMakeRange(0,
textView.textStorage.string.length)];
BOOL defaultValueChanged =
newViewProps.defaultValue != oldViewProps.defaultValue;

if (stylePropChanged) {
// now set the new config
config = newConfig;

// no emitting during styles reload
blockEmitting = YES;
// we already applied html with styles in default value
if (!defaultValueChanged) {
// all the text needs to be rebuilt
// we get the current html using old config, then switch to new config and
// replace text using the html this way, the newest config attributes are
// being used!

// the html needs to be generated using the old config
NSString *currentHtml = [parser
parseToHtmlFromRange:NSMakeRange(0,
textView.textStorage.string.length)];
// no emitting during styles reload
blockEmitting = YES;

// make sure everything is sound in the html
NSString *initiallyProcessedHtml =
[parser initiallyProcessHtml:currentHtml];
if (initiallyProcessedHtml != nullptr) {
[parser replaceWholeFromHtml:initiallyProcessedHtml
notifyAnyTextMayHaveBeenModified:!isFirstMount];
}

// make sure everything is sound in the html
NSString *initiallyProcessedHtml =
[parser initiallyProcessHtml:currentHtml];
if (initiallyProcessedHtml != nullptr) {
[parser replaceWholeFromHtml:initiallyProcessedHtml];
blockEmitting = NO;
}

blockEmitting = NO;

// fill the typing attributes with style props
defaultTypingAttributes[NSForegroundColorAttributeName] =
[config primaryColor];
Expand All @@ -578,7 +610,7 @@ - (void)updateProps:(Props::Shared const &)props

// default value - must be set before placeholder to make sure it correctly
// shows on first mount
if (newViewProps.defaultValue != oldViewProps.defaultValue) {
if (defaultValueChanged) {
NSString *newDefaultValue =
[NSString fromCppString:newViewProps.defaultValue];

Expand All @@ -589,7 +621,8 @@ - (void)updateProps:(Props::Shared const &)props
textView.text = newDefaultValue;
} else {
// we've got some seemingly proper html
[parser replaceWholeFromHtml:initiallyProcessedHtml];
[parser replaceWholeFromHtml:initiallyProcessedHtml
notifyAnyTextMayHaveBeenModified:!isFirstMount];
}
}

Expand Down Expand Up @@ -669,8 +702,13 @@ - (void)updateProps:(Props::Shared const &)props
_emitHtml = newViewProps.isOnChangeHtmlSet;

[super updateProps:props oldProps:oldProps];
// run the changes callback
[self anyTextMayHaveBeenModified];

// if default value changed it will be fired in default value update
// if this is initial mount it will be called in didMoveToWindow
if (!defaultValueChanged && !isFirstMount) {
// run the changes callback
[self anyTextMayHaveBeenModified];
}

// autofocus - needs to be done at the very end
if (isFirstMount && newViewProps.autoFocus) {
Expand Down Expand Up @@ -1002,7 +1040,8 @@ - (void)setValue:(NSString *)value {
textView.text = value;
} else {
// we've got some seemingly proper html
[parser replaceWholeFromHtml:initiallyProcessedHtml];
[parser replaceWholeFromHtml:initiallyProcessedHtml
notifyAnyTextMayHaveBeenModified:YES];
}

// set recentlyChangedRange and check for changes
Expand Down Expand Up @@ -1418,8 +1457,13 @@ - (void)_performRelayout {

- (void)didMoveToWindow {
[super didMoveToWindow];
// used to run all lifecycle callbacks
[self anyTextMayHaveBeenModified];

if (self.window && !_didRunInitialMount) {
_didRunInitialMount = YES;
[self layoutIfNeeded];
// Ideally we should remove this to match RN's uncontrolled inputs behaviour
[self anyTextMayHaveBeenModified];
}
}

// MARK: - UITextView delegate methods
Expand Down
14 changes: 14 additions & 0 deletions ios/inputParser/AttributedStringBuilder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface AttributedStringBuilder : NSObject

@property(nonatomic, weak) NSDictionary *stylesDict;

- (void)apply:(NSArray *)processedStyles
toAttributedString:(NSMutableAttributedString *)attributedString
offsetFromBeginning:(NSInteger)offset
conflictingStyles:
(NSDictionary<NSNumber *, NSArray<NSNumber *> *> *)conflictingStyles;

@end
82 changes: 82 additions & 0 deletions ios/inputParser/AttributedStringBuilder.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#import "AttributedStringBuilder.h"
#import "StyleHeaders.h"
#import "StylePair.h"

@implementation AttributedStringBuilder

- (void)apply:(NSArray *)processedStyles
toAttributedString:(NSMutableAttributedString *)attributedString
offsetFromBeginning:(NSInteger)offset
conflictingStyles:
(NSDictionary<NSNumber *, NSArray<NSNumber *> *> *)conflictingStyles {
[attributedString beginEditing];

for (NSArray *processedStylePair in processedStyles) {
NSNumber *type = processedStylePair[0];
StylePair *pair = processedStylePair[1];

NSRange pairRange = [pair.rangeValue rangeValue];
NSRange range = NSMakeRange(offset + pairRange.location, pairRange.length);
if (![self canApplyStyle:type
range:range
attributedString:attributedString
conflictingMap:conflictingStyles
stylesDict:self.stylesDict]) {
continue;
}

id<BaseStyleProtocol> style = self.stylesDict[type];

if ([type isEqualToNumber:@([LinkStyle getStyleType])]) {
NSString *text = [attributedString.string substringWithRange:range];
NSString *url = pair.styleValue;
BOOL isManual = [text isEqualToString:url];
[(LinkStyle *)style addLinkInAttributedString:attributedString
range:range
text:text
url:url
manual:isManual];

} else if ([type isEqualToNumber:@([MentionStyle getStyleType])]) {
[(MentionStyle *)style addMentionInAttributedString:attributedString
range:range
params:pair.styleValue];

} else if ([type isEqualToNumber:@([ImageStyle getStyleType])]) {
[(ImageStyle *)style addImageInAttributedString:attributedString
range:range
imageData:pair.styleValue];
} else {
[style addAttributesInAttributedString:attributedString range:range];
}
}

[attributedString endEditing];
}

- (BOOL)canApplyStyle:(NSNumber *)type
range:(NSRange)range
attributedString:(NSAttributedString *)string
conflictingMap:(NSDictionary<NSNumber *, NSArray<NSNumber *> *> *)confMap
stylesDict:
(NSDictionary<NSNumber *, id<BaseStyleProtocol>> *)stylesDict {
NSArray<NSNumber *> *conflicts = confMap[type];
if (!conflicts || conflicts.count == 0)
return YES;

for (NSNumber *conflictType in conflicts) {
id<BaseStyleProtocol> conflictStyle = stylesDict[conflictType];
if (!conflictStyle)
continue;

NSArray<StylePair *> *occurrences =
[conflictStyle findAllOccurencesInAttributedString:string range:range];

if (occurrences.count > 0) {
return NO;
}
}

return YES;
}
@end
7 changes: 7 additions & 0 deletions ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>

@interface ConvertHtmlToPlainTextAndStylesResult : NSObject
@property(nonatomic, strong) NSString *text;
@property(nonatomic, strong) NSArray *styles;
- (instancetype)initWithData:(NSString *)text styles:(NSArray *)styles;
@end
10 changes: 10 additions & 0 deletions ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "ConvertHtmlToPlainTextAndStylesResult.h"

@implementation ConvertHtmlToPlainTextAndStylesResult
- (instancetype)initWithData:(NSString *)text styles:(NSArray *)styles {
self = [super init];
_text = text;
_styles = styles;
return self;
}
@end
12 changes: 12 additions & 0 deletions ios/inputParser/HtmlBuilder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import "EnrichedTextInputView.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface HtmlBuilder : NSObject

@property(nonatomic, weak) NSDictionary *stylesDict;
@property(nonatomic, weak) EnrichedTextInputView *input;

- (NSString *)htmlFromRange:(NSRange)range;

@end
Loading
Loading