diff --git a/mobile/lib/pages/common/headers_settings.page.dart b/mobile/lib/pages/common/headers_settings.page.dart index 24c5da1c10..7860b8f78d 100644 --- a/mobile/lib/pages/common/headers_settings.page.dart +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -132,6 +132,8 @@ class HeaderKeyValueSettings extends StatelessWidget { border: const OutlineInputBorder(), ), autocorrect: false, + smartDashesType: SmartDashesType.disabled, + smartQuotesType: SmartQuotesType.disabled, onChanged: (headerKey) { header.key = headerKey; }, @@ -160,6 +162,8 @@ class HeaderKeyValueSettings extends StatelessWidget { border: const OutlineInputBorder(), ), autocorrect: false, + smartDashesType: SmartDashesType.disabled, + smartQuotesType: SmartQuotesType.disabled, onChanged: (headerValue) { header.value = headerValue; }, diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index 5185c5c318..aa9750a2a5 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -493,14 +493,12 @@ class LoginForm extends HookConsumerWidget { builder: (context, form) => Column( spacing: ImmichSpacing.md, children: [ - ImmichTextInput( + ImmichEmailInput( controller: emailController, label: context.t.email, hintText: context.t.login_form_email_hint, validator: _validateEmail, keyboardAction: TextInputAction.next, - keyboardType: TextInputType.emailAddress, - autofillHints: const [AutofillHints.email], onSubmit: (_) => passwordFocusNode.requestFocus(), ), ImmichPasswordInput( diff --git a/mobile/packages/ui/lib/immich_ui.dart b/mobile/packages/ui/lib/immich_ui.dart index 8ea88135e5..85875717a3 100644 --- a/mobile/packages/ui/lib/immich_ui.dart +++ b/mobile/packages/ui/lib/immich_ui.dart @@ -1,6 +1,7 @@ export 'src/color_override.dart'; export 'src/components/close_button.dart'; export 'src/components/column_button.dart'; +export 'src/components/email_input.dart'; export 'src/components/form.dart'; export 'src/components/formatted_text.dart'; export 'src/components/icon_button.dart'; diff --git a/mobile/packages/ui/lib/src/components/email_input.dart b/mobile/packages/ui/lib/src/components/email_input.dart new file mode 100644 index 0000000000..27d79e9f0e --- /dev/null +++ b/mobile/packages/ui/lib/src/components/email_input.dart @@ -0,0 +1,26 @@ +import 'package:flutter/services.dart'; +import 'package:immich_ui/src/components/text_input.dart'; + +/// An ImmichTextInput customized for receiving email addresses +class ImmichEmailInput extends ImmichTextInput { + const ImmichEmailInput({ + super.key, + super.controller, + super.focusNode, + super.label, + super.hintText, + super.validator, + super.onSubmit, + super.keyboardAction, + super.suffixIcon, + super.enabled, + super.autofocus, + super.autovalidateMode, + }) : super( + keyboardType: .emailAddress, + autofillHints: const [AutofillHints.email], + autocorrect: false, + smartDashesType: .disabled, + smartQuotesType: .disabled, + ); +} diff --git a/mobile/packages/ui/lib/src/components/password_input.dart b/mobile/packages/ui/lib/src/components/password_input.dart index d08b3de99d..aa66d43b4d 100644 --- a/mobile/packages/ui/lib/src/components/password_input.dart +++ b/mobile/packages/ui/lib/src/components/password_input.dart @@ -47,6 +47,9 @@ class _ImmichPasswordInputState extends State { onSubmit: widget.onSubmit, keyboardAction: widget.keyboardAction, obscureText: !_visible, + autocorrect: false, + smartDashesType: .disabled, + smartQuotesType: .disabled, suffixIcon: IconButton( onPressed: _toggleVisibility, icon: Icon(_visible ? Icons.visibility_off_rounded : Icons.visibility_rounded), diff --git a/mobile/packages/ui/lib/src/previews/email_input.dart b/mobile/packages/ui/lib/src/previews/email_input.dart new file mode 100644 index 0000000000..b9d1d64ea0 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/email_input.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/email_input.dart'; +import 'package:immich_ui/src/previews.dart'; + +@ImmichPreview(group: 'EmailInput', name: 'Basic') +Widget previewEmailInput() => const _PreviewEmailInput(); + +class _PreviewEmailInput extends StatefulWidget { + const _PreviewEmailInput(); + + @override + State<_PreviewEmailInput> createState() => _PreviewEmailInputState(); +} + +class _PreviewEmailInputState extends State<_PreviewEmailInput> { + final _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ImmichEmailInput(label: 'Email', hintText: 'user@immich.app', controller: _controller); + } +} diff --git a/mobile/packages/ui/test/smart_punctuation_test.dart b/mobile/packages/ui/test/smart_punctuation_test.dart new file mode 100644 index 0000000000..93f1f7a92d --- /dev/null +++ b/mobile/packages/ui/test/smart_punctuation_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_ui/src/components/email_input.dart'; +import 'package:immich_ui/src/components/password_input.dart'; + +import 'test_utils.dart'; + +void main() { + EditableText editable(WidgetTester tester) => tester.widget(find.byType(EditableText)); + + testWidgets('ImmichEmailInput disables smart punctuation', (tester) async { + await tester.pumpTestWidget(const ImmichEmailInput()); + + expect(editable(tester).smartDashesType, SmartDashesType.disabled); + expect(editable(tester).smartQuotesType, SmartQuotesType.disabled); + }); + + testWidgets('ImmichPasswordInput disables smart punctuation', (tester) async { + await tester.pumpTestWidget(const ImmichPasswordInput()); + + expect(editable(tester).obscureText, isTrue); + expect(editable(tester).smartDashesType, SmartDashesType.disabled); + expect(editable(tester).smartQuotesType, SmartQuotesType.disabled); + + await tester.tap(find.byIcon(Icons.visibility_rounded)); + await tester.pump(); + + expect(editable(tester).obscureText, isFalse); + expect(editable(tester).smartDashesType, SmartDashesType.disabled); + expect(editable(tester).smartQuotesType, SmartQuotesType.disabled); + }); +}