Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#include "unicode/uchar.h"
Copy link
Member

Choose a reason for hiding this comment

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

@dnfield since you jumped in, how do you feel about including this library? I've been trying to think of another way to check if it's an emoji without importing this but also reinventing the wheel. I guess it's okay?

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems fine to me. This is what ICU is for and there's no getting around having ICU in a flutter app


#include "flutter/fml/logging.h"
#include "flutter/fml/platform/darwin/string_range_sanitization.h"

Expand Down Expand Up @@ -1896,6 +1898,22 @@ - (void)deleteBackward {
NSRange oldRange = ((FlutterTextRange*)oldSelectedRange).range;
if (oldRange.location > 0) {
NSRange newRange = NSMakeRange(oldRange.location - 1, 1);

// We should check if the last character is a part of emoji.
// If so, we must delete the entire emoji to prevent the text from being malformed.
NSRange charRange = fml::RangeForCharacterAtIndex(self.text, oldRange.location - 1);
UChar32 codePoint;
BOOL gotCodePoint = [self.text getBytes:&codePoint
maxLength:sizeof(codePoint)
usedLength:NULL
encoding:NSUTF32StringEncoding
options:kNilOptions
range:charRange
remainingRange:NULL];
if (gotCodePoint && u_hasBinaryProperty(codePoint, UCHAR_EMOJI)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this getting applied only to emoji?

Aren't there other multi-byte characters that are not emoji that we'd want to apply this to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From #34508 (comment), languages with diacritical marks will expect a backward deletion should only delete one part of the composed character, such as Thai and Hebrew.

We have two ways to distinguish this behavior from emoji's:

  1. check if the byte is a diacritical mark (and other properties I don't know yet)
  2. check if the byte is not in an emoji

I chose the second one to keep consistency with android (see FlutterTextUtils.getOffsetBefore)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahhh ok this makes sense.

newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location);
}

_selectedTextRange = [[FlutterTextRange rangeWithNSRange:newRange] copy];
[oldSelectedRange release];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,41 @@ - (void)testStandardEditActions {
XCTAssertEqualObjects(substring, @"bbbbaaaabbbbaaaa");
}

- (void)testDeletingBackward {
NSDictionary* config = self.mutableTemplateCopy;
[self setClientId:123 configuration:config];
NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
FlutterTextInputView* inputView = inputFields[0];

[inputView insertText:@"ឹ😀 text 🥰👨‍👩‍👧‍👦🇺🇳ดี "];
[inputView deleteBackward];
[inputView deleteBackward];

// Thai vowel is removed.
XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰👨‍👩‍👧‍👦🇺🇳ด");
[inputView deleteBackward];
XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰👨‍👩‍👧‍👦🇺🇳");
[inputView deleteBackward];
XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰👨‍👩‍👧‍👦");
[inputView deleteBackward];
XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰");
[inputView deleteBackward];

XCTAssertEqualObjects(inputView.text, @"ឹ😀 text ");
[inputView deleteBackward];
[inputView deleteBackward];
[inputView deleteBackward];
[inputView deleteBackward];
[inputView deleteBackward];
[inputView deleteBackward];

XCTAssertEqualObjects(inputView.text, @"ឹ😀");
[inputView deleteBackward];
XCTAssertEqualObjects(inputView.text, @"ឹ");
[inputView deleteBackward];
XCTAssertEqualObjects(inputView.text, @"");
}

- (void)testPastingNonTextDisallowed {
NSDictionary* config = self.mutableTemplateCopy;
[self setClientId:123 configuration:config];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@
ReferencedContainer = "container:IosUnitTests.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "--icu-data-file-path=Frameworks/Flutter.framework/icudtl.dat"
isEnabled = "YES">
Comment on lines +83 to +84
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed?

Copy link
Contributor Author

@yeatse yeatse Jul 23, 2022

Choose a reason for hiding this comment

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

Yes. Without this the test will fail due to failing to load the ICU data file. Maybe we should file another issue to get rid of this later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is fine - in other tests we use the command line argument to load the ICU data file (see run_tests.py).

</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down