From e0d7b588b6ab5ef8237a1ad7df1291afc0450c04 Mon Sep 17 00:00:00 2001 From: NabilaWorks Date: Wed, 15 Jan 2025 07:15:15 +0700 Subject: [PATCH] Feature/twitter keyboard (#161025) Fixes https://github.com/flutter/flutter/issues/64801 Example : ```dart import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Centered TextField Example', theme: ThemeData(primarySwatch: Colors.blue), home: CenteredTextFieldScreen(), ); } } class CenteredTextFieldScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Centered TextField')), body: Center( child: Padding( padding: const EdgeInsets.all(16.0), child: TextField( keyboardType: TextInputType.twitter, decoration: InputDecoration( border: OutlineInputBorder(), hintText: 'Enter some text here', ), ), ), ), ); } } ``` https://github.com/user-attachments/assets/5a2a2a4a-6994-44b1-bb0e-395c24012ef8 https://github.com/user-attachments/assets/aefc7bc5-a997-4e0f-a74e-a39a4517c898 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Jenn Magder --- .../systemchannels/TextInputChannel.java | 3 +- .../plugin/editing/TextInputPlugin.java | 3 +- .../plugin/editing/TextInputPluginTest.java | 35 +++++++++++++++++++ .../Source/FlutterTextInputPlugin.mm | 3 ++ .../Source/FlutterTextInputPluginTest.mm | 14 ++++++++ .../flutter/lib/src/services/text_input.dart | 11 ++++++ .../test/services/text_input_test.dart | 5 +++ 7 files changed, 72 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 641d3f37cc2..cee87470b86 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -718,7 +718,8 @@ public class TextInputChannel { URL("TextInputType.url"), VISIBLE_PASSWORD("TextInputType.visiblePassword"), NONE("TextInputType.none"), - WEB_SEARCH("TextInputType.webSearch"); + WEB_SEARCH("TextInputType.webSearch"), + TWITTER("TextInputType.twitter"); static TextInputType fromValue(@NonNull String encodedName) throws NoSuchFieldException { for (TextInputType textInputType : TextInputType.values()) { diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 4f3adcec4d8..c832fde195e 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -250,7 +250,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch int textType = InputType.TYPE_CLASS_TEXT; if (type.type == TextInputChannel.TextInputType.MULTILINE) { textType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; - } else if (type.type == TextInputChannel.TextInputType.EMAIL_ADDRESS) { + } else if (type.type == TextInputChannel.TextInputType.EMAIL_ADDRESS + || type.type == TextInputChannel.TextInputType.TWITTER) { textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; } else if (type.type == TextInputChannel.TextInputType.URL || type.type == TextInputChannel.TextInputType.WEB_SEARCH) { diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index d10df7bf1ae..eb5baaba84b 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -1390,6 +1390,41 @@ public class TextInputPluginTest { editorInfo.inputType, InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); } + @Test + public void showTextInput_textInputTypeTwitter() { + TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); + View testView = new View(ctx); + DartExecutor dartExecutor = mock(DartExecutor.class); + TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class)); + TextInputPlugin textInputPlugin = + new TextInputPlugin( + testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class)); + textInputPlugin.setTextInputClient( + 0, + new TextInputChannel.Configuration( + false, + false, + true, + true, + false, + TextInputChannel.TextCapitalization.NONE, + new TextInputChannel.InputType(TextInputChannel.TextInputType.TWITTER, false, false), + null, + null, + null, + null, + null)); + + EditorInfo editorInfo = new EditorInfo(); + InputConnection connection = + textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), editorInfo); + + assertEquals( + editorInfo.inputType, + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + } + @Test public void inputConnection_textInputTypeMultilineAndSuggestionsDisabled() { // Regression test for https://github.com/flutter/flutter/issues/71679. diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 9358431cf5a..66d2d722ecf 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -143,6 +143,9 @@ static UIKeyboardType ToUIKeyboardType(NSDictionary* type) { if ([inputType isEqualToString:@"TextInputType.webSearch"]) { return UIKeyboardTypeWebSearch; } + if ([inputType isEqualToString:@"TextInputType.twitter"]) { + return UIKeyboardTypeTwitter; + } return UIKeyboardTypeDefault; } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index be00f6e1e63..5c14a6cfd65 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -321,6 +321,20 @@ FLUTTER_ASSERT_ARC XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeWebSearch); } +- (void)testKeyboardTypeTwitter { + NSDictionary* config = self.mutableTemplateCopy; + [config setValue:@{@"name" : @"TextInputType.twitter"} forKey:@"inputType"]; + [self setClientId:123 configuration:config]; + + // Find all the FlutterTextInputViews we created. + NSArray* inputFields = self.installedInputViews; + + FlutterTextInputView* inputView = inputFields[0]; + + // Verify keyboardType is set to the value specified in config. + XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeTwitter); +} + - (void)testVisiblePasswordUseAlphanumeric { NSDictionary* config = self.mutableTemplateCopy; [config setValue:@{@"name" : @"TextInputType.visiblePassword"} forKey:@"inputType"]; diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index cf0fd67e7e1..7e5a38c59ad 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -203,6 +203,15 @@ class TextInputType { /// On Android this is remapped to the [url] keyboard type as it always shows a space bar. static const TextInputType webSearch = TextInputType._(11); + /// Optimized for social media. + /// + /// Requests a keyboard that includes keys useful for handles and tags. + /// + /// On iOS, requests a default keyboard with ready access to the "@" and "#" keys. + /// + /// On Android this is remapped to the [emailAddress] keyboard type as it always shows the "@" key. + static const TextInputType twitter = TextInputType._(12); + /// All possible enum values. static const List values = [ text, @@ -217,6 +226,7 @@ class TextInputType { streetAddress, none, webSearch, + twitter, ]; // Corresponding string name for each of the [values]. @@ -233,6 +243,7 @@ class TextInputType { 'address', 'none', 'webSearch', + 'twitter', ]; // Enum value name, this is what enum.toString() would normally return. diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart index b51f39b3c6f..b4cdc24ce82 100644 --- a/packages/flutter/test/services/text_input_test.dart +++ b/packages/flutter/test/services/text_input_test.dart @@ -447,6 +447,10 @@ void main() { TextInputType.webSearch.toString(), 'TextInputType(name: TextInputType.webSearch, signed: null, decimal: null)', ); + expect( + TextInputType.twitter.toString(), + 'TextInputType(name: TextInputType.twitter, signed: null, decimal: null)', + ); expect(text == number, false); expect(number == number2, true); @@ -476,6 +480,7 @@ void main() { expect(TextInputType.streetAddress.index, 9); expect(TextInputType.none.index, 10); expect(TextInputType.webSearch.index, 11); + expect(TextInputType.twitter.index, 12); expect( TextEditingValue.empty.toString(),