TF-1884 Support RTL mode for TypeAheadFormField
(cherry picked from commit e375d87d5d485ecb717b1a8afee2b7bf741fbeac)
This commit is contained in:
@@ -0,0 +1,110 @@
|
|||||||
|
import 'package:core/utils/direction_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
|
|
||||||
|
class TypeAheadFormFieldBuilder<T> extends StatefulWidget {
|
||||||
|
|
||||||
|
final TextDirection textDirection;
|
||||||
|
final Duration debounceDuration;
|
||||||
|
final SuggestionsCallback<T> suggestionsCallback;
|
||||||
|
final ItemBuilder<T> itemBuilder;
|
||||||
|
final SuggestionSelectionCallback<T> onSuggestionSelected;
|
||||||
|
final SuggestionsBoxDecoration suggestionsBoxDecoration;
|
||||||
|
final WidgetBuilder? noItemsFoundBuilder;
|
||||||
|
final bool hideOnEmpty;
|
||||||
|
final bool hideOnError;
|
||||||
|
final bool hideOnLoading;
|
||||||
|
final TextEditingController? controller;
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
final ValueChanged<String>? onTextChange;
|
||||||
|
final ValueChanged<String>? onTextSubmitted;
|
||||||
|
final TextInputAction? textInputAction;
|
||||||
|
final bool autocorrect;
|
||||||
|
final List<String>? autofillHints;
|
||||||
|
final TextInputType keyboardType;
|
||||||
|
final InputDecoration decoration;
|
||||||
|
|
||||||
|
const TypeAheadFormFieldBuilder({
|
||||||
|
super.key,
|
||||||
|
required this.suggestionsCallback,
|
||||||
|
required this.itemBuilder,
|
||||||
|
required this.onSuggestionSelected,
|
||||||
|
this.suggestionsBoxDecoration = const SuggestionsBoxDecoration(),
|
||||||
|
this.textDirection = TextDirection.ltr,
|
||||||
|
this.debounceDuration = const Duration(milliseconds: 300),
|
||||||
|
this.decoration = const InputDecoration(),
|
||||||
|
this.noItemsFoundBuilder,
|
||||||
|
this.hideOnEmpty = false,
|
||||||
|
this.hideOnError = false,
|
||||||
|
this.hideOnLoading = false,
|
||||||
|
this.autocorrect = false,
|
||||||
|
this.keyboardType = TextInputType.text,
|
||||||
|
this.controller,
|
||||||
|
this.focusNode,
|
||||||
|
this.autofillHints,
|
||||||
|
this.textInputAction,
|
||||||
|
this.onTextChange,
|
||||||
|
this.onTextSubmitted,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TypeAheadFormFieldBuilder<T>> createState() => _TypeAheadFormFieldBuilderState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TypeAheadFormFieldBuilderState<T> extends State<TypeAheadFormFieldBuilder<T>> {
|
||||||
|
|
||||||
|
late TextEditingController _controller;
|
||||||
|
late TextDirection _textDirection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_textDirection = widget.textDirection;
|
||||||
|
_controller = widget.controller ?? TextEditingController();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TypeAheadFormField<T>(
|
||||||
|
key: widget.key,
|
||||||
|
textFieldConfiguration: TextFieldConfiguration(
|
||||||
|
controller: widget.controller,
|
||||||
|
textInputAction: widget.textInputAction,
|
||||||
|
autocorrect: widget.autocorrect,
|
||||||
|
autofillHints: widget.autofillHints,
|
||||||
|
keyboardType: widget.keyboardType,
|
||||||
|
decoration: widget.decoration,
|
||||||
|
textDirection: _textDirection,
|
||||||
|
onChanged: (value) {
|
||||||
|
widget.onTextChange?.call(value);
|
||||||
|
if (value.isNotEmpty) {
|
||||||
|
final directionByText = DirectionUtils.getDirectionByEndsText(value);
|
||||||
|
if (directionByText != _textDirection) {
|
||||||
|
setState(() {
|
||||||
|
_textDirection = directionByText;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSubmitted: widget.onTextSubmitted
|
||||||
|
),
|
||||||
|
debounceDuration: widget.debounceDuration,
|
||||||
|
suggestionsCallback: widget.suggestionsCallback,
|
||||||
|
itemBuilder: widget.itemBuilder,
|
||||||
|
onSuggestionSelected: widget.onSuggestionSelected,
|
||||||
|
suggestionsBoxDecoration: widget.suggestionsBoxDecoration,
|
||||||
|
noItemsFoundBuilder: widget.noItemsFoundBuilder,
|
||||||
|
hideOnEmpty: widget.hideOnEmpty,
|
||||||
|
hideOnError: widget.hideOnError,
|
||||||
|
hideOnLoading: widget.hideOnLoading,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (widget.controller == null) {
|
||||||
|
_controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -194,10 +194,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_keyboard_visibility
|
name: flutter_keyboard_visibility
|
||||||
sha256: "86b71bbaffa38e885f5c21b1182408b9be6951fd125432cf6652c636254cef2d"
|
sha256: "4983655c26ab5b959252ee204c2fffa4afeb4413cd030455194ec0caa3b8e7cb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.0"
|
version: "5.4.1"
|
||||||
flutter_keyboard_visibility_linux:
|
flutter_keyboard_visibility_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -259,6 +259,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_typeahead:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_typeahead
|
||||||
|
sha256: f31211a8536f87908c3dcbdb88666e2f4d77f5f06c2b3a48eaad5599969ff32d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.6.0"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ dependencies:
|
|||||||
# flutter_localizations depends on intl 0.17.0
|
# flutter_localizations depends on intl 0.17.0
|
||||||
intl: 0.17.0
|
intl: 0.17.0
|
||||||
|
|
||||||
|
flutter_typeahead: 4.6.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@@ -83,6 +85,10 @@ dev_dependencies:
|
|||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
|
||||||
|
flutter_keyboard_visibility: 5.4.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
# The following line ensures that the Material Icons font is
|
# The following line ensures that the Material Icons font is
|
||||||
# included with your application, so that you can use the icons in
|
# included with your application, so that you can use the icons in
|
||||||
|
|||||||
+13
-13
@@ -1,5 +1,7 @@
|
|||||||
|
|
||||||
import 'package:core/core.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
|
||||||
|
import 'package:core/utils/platform_info.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||||
@@ -40,18 +42,16 @@ class IdentityInputWithDropListFieldBuilder extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: AppColor.colorContentEmail)),
|
color: AppColor.colorContentEmail)),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TypeAheadFormField<EmailAddress>(
|
TypeAheadFormFieldBuilder<EmailAddress>(
|
||||||
textFieldConfiguration: TextFieldConfiguration(
|
focusNode: focusNode,
|
||||||
focusNode: focusNode,
|
controller: editingController,
|
||||||
controller: editingController,
|
textInputAction: TextInputAction.done,
|
||||||
textInputAction: TextInputAction.done,
|
decoration: (IdentityInputDecorationBuilder()
|
||||||
decoration: (IdentityInputDecorationBuilder()
|
..setContentPadding(const EdgeInsets.symmetric(
|
||||||
..setContentPadding(const EdgeInsets.symmetric(
|
vertical: PlatformInfo.isWeb ? 16 : 12,
|
||||||
vertical: BuildUtils.isWeb ? 16 : 12,
|
horizontal: 12))
|
||||||
horizontal: 12))
|
..setErrorText(_error))
|
||||||
..setErrorText(_error))
|
.build(),
|
||||||
.build()
|
|
||||||
),
|
|
||||||
debounceDuration: const Duration(milliseconds: 500),
|
debounceDuration: const Duration(milliseconds: 500),
|
||||||
suggestionsCallback: (pattern) async {
|
suggestionsCallback: (pattern) async {
|
||||||
if (onChangeInputSuggestionAction != null) {
|
if (onChangeInputSuggestionAction != null) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/presentation/resources/image_paths.dart';
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
import 'package:core/presentation/utils/responsive_utils.dart';
|
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||||
|
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -106,32 +107,26 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
|
|||||||
Widget buildUserNameInput(BuildContext context) {
|
Widget buildUserNameInput(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 24, right: 24, left: 24),
|
padding: const EdgeInsets.only(bottom: 24, right: 24, left: 24),
|
||||||
child: TypeAheadFormField<RecentLoginUsername>(
|
child: TypeAheadFormFieldBuilder<RecentLoginUsername>(
|
||||||
key: const Key('login_username_input'),
|
key: const Key('login_username_input'),
|
||||||
textFieldConfiguration: TextFieldConfiguration(
|
controller: loginController.usernameInputController,
|
||||||
controller: loginController.usernameInputController,
|
onTextChange: loginController.setUserNameText,
|
||||||
onChanged: (value) => loginController.setUserNameText(value),
|
textInputAction: TextInputAction.next,
|
||||||
textInputAction: TextInputAction.next,
|
autocorrect: false,
|
||||||
autocorrect: false,
|
autofillHints: const [AutofillHints.email],
|
||||||
autofillHints: [AutofillHints.email],
|
keyboardType: TextInputType.emailAddress,
|
||||||
keyboardType: TextInputType.emailAddress,
|
decoration: (LoginInputDecorationBuilder()
|
||||||
decoration: (LoginInputDecorationBuilder()
|
..setLabelText(AppLocalizations.of(context).email)
|
||||||
..setLabelText(AppLocalizations.of(context).email)
|
..setHintText(AppLocalizations.of(context).email))
|
||||||
..setHintText(AppLocalizations.of(context).email))
|
.build(),
|
||||||
.build(),
|
|
||||||
),
|
|
||||||
debounceDuration: const Duration(milliseconds: 300),
|
debounceDuration: const Duration(milliseconds: 300),
|
||||||
suggestionsCallback: (pattern) async {
|
suggestionsCallback: loginController.getAllRecentLoginUsernameAction,
|
||||||
return await loginController.getAllRecentLoginUsernameAction(pattern);
|
itemBuilder: (context, loginUsername) => RecentItemTileWidget(loginUsername, imagePath: imagePaths),
|
||||||
},
|
|
||||||
itemBuilder: (context, loginUsername) =>
|
|
||||||
RecentItemTileWidget(loginUsername, imagePath: imagePaths),
|
|
||||||
onSuggestionSelected: (recentUsername) {
|
onSuggestionSelected: (recentUsername) {
|
||||||
loginController.setUsername(recentUsername.username);
|
loginController.setUsername(recentUsername.username);
|
||||||
passFocusNode.requestFocus();
|
passFocusNode.requestFocus();
|
||||||
},
|
},
|
||||||
suggestionsBoxDecoration: const SuggestionsBoxDecoration(
|
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(14))),
|
|
||||||
noItemsFoundBuilder: (context) => const SizedBox(),
|
noItemsFoundBuilder: (context) => const SizedBox(),
|
||||||
hideOnEmpty: true,
|
hideOnEmpty: true,
|
||||||
hideOnError: true,
|
hideOnError: true,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
@@ -135,27 +136,23 @@ class LoginView extends BaseLoginView {
|
|||||||
Widget _buildUrlInput(BuildContext context) {
|
Widget _buildUrlInput(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 24, left: 24, bottom: 24),
|
padding: const EdgeInsets.only(right: 24, left: 24, bottom: 24),
|
||||||
child: TypeAheadFormField<RecentLoginUrl>(
|
child: TypeAheadFormFieldBuilder<RecentLoginUrl>(
|
||||||
textFieldConfiguration: TextFieldConfiguration(
|
controller: loginController.urlInputController,
|
||||||
controller: loginController.urlInputController,
|
textInputAction: TextInputAction.next,
|
||||||
textInputAction: TextInputAction.next,
|
keyboardType: TextInputType.url,
|
||||||
keyboardType: TextInputType.url,
|
onTextSubmitted: (value) => controller.handleNextInUrlInputFormPress(),
|
||||||
onSubmitted: (value) => controller.handleNextInUrlInputFormPress(),
|
decoration: (LoginInputDecorationBuilder()
|
||||||
decoration: (LoginInputDecorationBuilder()
|
..setLabelText(AppLocalizations.of(context).prefix_https)
|
||||||
..setLabelText(AppLocalizations.of(context).prefix_https)
|
..setPrefixText(AppLocalizations.of(context).prefix_https))
|
||||||
..setPrefixText(AppLocalizations.of(context).prefix_https))
|
.build(),
|
||||||
.build()
|
|
||||||
),
|
|
||||||
debounceDuration: const Duration(milliseconds: 300),
|
debounceDuration: const Duration(milliseconds: 300),
|
||||||
suggestionsCallback: (pattern) async {
|
suggestionsCallback: (pattern) async {
|
||||||
loginController.formatUrl(pattern);
|
loginController.formatUrl(pattern);
|
||||||
return loginController.getAllRecentLoginUrlAction(pattern);
|
return loginController.getAllRecentLoginUrlAction(pattern);
|
||||||
},
|
},
|
||||||
itemBuilder: (context, loginUrl) =>
|
itemBuilder: (context, loginUrl) => RecentItemTileWidget(loginUrl, imagePath: imagePaths),
|
||||||
RecentItemTileWidget(loginUrl, imagePath: imagePaths),
|
|
||||||
onSuggestionSelected: (loginUrl) => controller.formatUrl(loginUrl.url),
|
onSuggestionSelected: (loginUrl) => controller.formatUrl(loginUrl.url),
|
||||||
suggestionsBoxDecoration: const SuggestionsBoxDecoration(
|
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(14))),
|
|
||||||
noItemsFoundBuilder: (context) => const SizedBox(),
|
noItemsFoundBuilder: (context) => const SizedBox(),
|
||||||
hideOnEmpty: true,
|
hideOnEmpty: true,
|
||||||
hideOnError: true,
|
hideOnError: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user