diff --git a/.gitignore b/.gitignore index 9e58663..9fc94f1 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,8 @@ migrate_working_dir/ .flutter-plugins-dependencies .packages build/ + +# Generated files +**/generated_plugin_registrant.* +**/generated_plugins.* +**/GeneratedPluginRegistrant.* diff --git a/lib/src/components/_supa_password_field.dart b/lib/src/components/_supa_password_field.dart new file mode 100644 index 0000000..8cc66b9 --- /dev/null +++ b/lib/src/components/_supa_password_field.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; + +/// Internal password field component with visibility toggle +class SupaPasswordField extends StatefulWidget { + /// Controller for the password field + final TextEditingController controller; + + /// Validator function for the password field + final String? Function(String?)? validator; + + /// Label text for the password field + final String labelText; + + /// Prefix icon for the password field + final Widget? prefixIcon; + + /// Autofill hints for the password field + final Iterable? autofillHints; + + /// Text input action for the password field + final TextInputAction? textInputAction; + + /// Callback when field is submitted + final void Function(String)? onFieldSubmitted; + + /// Whether the field should auto-validate + final AutovalidateMode? autovalidateMode; + + const SupaPasswordField({ + super.key, + required this.controller, + this.validator, + required this.labelText, + this.prefixIcon, + this.autofillHints, + this.textInputAction, + this.onFieldSubmitted, + this.autovalidateMode, + }); + + @override + State createState() => _SupaPasswordFieldState(); +} + +class _SupaPasswordFieldState extends State { + bool _obscureText = true; + + @override + Widget build(BuildContext context) { + return TextFormField( + controller: widget.controller, + validator: widget.validator, + obscureText: _obscureText, + autofillHints: widget.autofillHints, + textInputAction: widget.textInputAction, + onFieldSubmitted: widget.onFieldSubmitted, + autovalidateMode: widget.autovalidateMode, + decoration: InputDecoration( + prefixIcon: widget.prefixIcon, + label: Text(widget.labelText), + suffixIcon: Tooltip( + message: _obscureText ? 'Show password' : 'Hide password', + child: IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + ), + onPressed: () { + setState(() { + _obscureText = !_obscureText; + }); + }, + ), + ), + ), + ); + } +} diff --git a/lib/src/components/supa_email_auth.dart b/lib/src/components/supa_email_auth.dart index 0fd2396..803de27 100644 --- a/lib/src/components/supa_email_auth.dart +++ b/lib/src/components/supa_email_auth.dart @@ -1,5 +1,6 @@ import 'package:email_validator/email_validator.dart'; import 'package:flutter/material.dart'; +import 'package:supabase_auth_ui/src/components/_supa_password_field.dart'; import 'package:supabase_auth_ui/src/localizations/supa_email_auth_localization.dart'; import 'package:supabase_auth_ui/src/utils/constants.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -328,7 +329,10 @@ class _SupaEmailAuthState extends State { ), if (!_isRecoveringPassword) ...[ spacer(16), - TextFormField( + SupaPasswordField( + controller: _passwordController, + labelText: localization.enterPassword, + prefixIcon: widget.prefixIconPassword, autofillHints: _isSigningIn ? [AutofillHints.password] : [AutofillHints.newPassword], @@ -343,12 +347,6 @@ class _SupaEmailAuthState extends State { } return null; }, - decoration: InputDecoration( - prefixIcon: widget.prefixIconPassword, - label: Text(localization.enterPassword), - ), - obscureText: true, - controller: _passwordController, onFieldSubmitted: (_) { if (widget.metadataFields == null || _isSigningIn) { _signInSignUp(); @@ -357,13 +355,10 @@ class _SupaEmailAuthState extends State { ), if (widget.showConfirmPasswordField && !_isSigningIn) ...[ spacer(16), - TextFormField( + SupaPasswordField( controller: _confirmPasswordController, - decoration: InputDecoration( - prefixIcon: widget.prefixIconPassword, - label: Text(localization.confirmPassword), - ), - obscureText: true, + labelText: localization.confirmPassword, + prefixIcon: widget.prefixIconPassword, validator: (value) { if (value != _passwordController.text) { return localization.confirmPasswordError; diff --git a/lib/src/components/supa_phone_auth.dart b/lib/src/components/supa_phone_auth.dart index a6a0935..0f6728f 100644 --- a/lib/src/components/supa_phone_auth.dart +++ b/lib/src/components/supa_phone_auth.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:supabase_auth_ui/src/components/_supa_password_field.dart'; import 'package:supabase_auth_ui/src/utils/constants.dart'; import 'package:supabase_auth_ui/supabase_auth_ui.dart'; @@ -10,7 +11,7 @@ class SupaPhoneAuth extends StatefulWidget { /// Method to be called when the auth action is success final void Function(AuthResponse response) onSuccess; - /// Method to be called when the auth action threw an excepction + /// Method to be called when the auth action threw an exception final void Function(Object error)? onError; /// Localization for the form @@ -71,7 +72,10 @@ class _SupaPhoneAuthState extends State { controller: _phone, ), spacer(16), - TextFormField( + SupaPasswordField( + controller: _password, + labelText: localization.enterPassword, + prefixIcon: const Icon(Icons.lock), autofillHints: isSigningIn ? [AutofillHints.password] : [AutofillHints.newPassword], @@ -82,12 +86,6 @@ class _SupaPhoneAuthState extends State { } return null; }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.lock), - label: Text(localization.enterPassword), - ), - obscureText: true, - controller: _password, ), spacer(16), ElevatedButton( diff --git a/lib/src/components/supa_reset_password.dart b/lib/src/components/supa_reset_password.dart index 7aa0dd1..90ca092 100644 --- a/lib/src/components/supa_reset_password.dart +++ b/lib/src/components/supa_reset_password.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:supabase_auth_ui/src/components/_supa_password_field.dart'; import 'package:supabase_auth_ui/src/localizations/supa_reset_password_localization.dart'; import 'package:supabase_auth_ui/src/utils/constants.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -47,7 +48,10 @@ class _SupaResetPasswordState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - TextFormField( + SupaPasswordField( + controller: _password, + labelText: localization.enterPassword, + prefixIcon: const Icon(Icons.lock), autofillHints: const [AutofillHints.newPassword], validator: (value) { if (value == null || value.isEmpty || value.length < 6) { @@ -55,11 +59,6 @@ class _SupaResetPasswordState extends State { } return null; }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.lock), - label: Text(localization.enterPassword), - ), - controller: _password, ), spacer(16), ElevatedButton(