Skip to content

Commit 843baec

Browse files
committed
Added password manager support for login
1 parent 1b6d733 commit 843baec

3 files changed

Lines changed: 135 additions & 123 deletions

File tree

lib/components/fields/app_field.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class AppField extends StatelessWidget {
2020
this.minLines,
2121
this.maxLines = 1,
2222
this.label,
23+
this.autofillHints,
2324
}) : super(key: key);
2425

2526
final Widget? prefix;
@@ -39,15 +40,12 @@ class AppField extends StatelessWidget {
3940
final Color? color;
4041
final Function(String)? onFieldSubmitted;
4142
final Function(String)? onChanged;
43+
final Iterable<String>? autofillHints;
4244

4345
@override
4446
Widget build(BuildContext context) {
45-
return Container(
46-
margin: margin,
47-
decoration: BoxDecoration(
48-
borderRadius: BorderRadius.circular(10.0),
49-
color: color ?? Theme.of(context).inputDecorationTheme.fillColor,
50-
),
47+
return Padding(
48+
padding: margin ?? EdgeInsets.zero,
5149
child: TextFormField(
5250
controller: controller,
5351
focusNode: focusNode,
@@ -58,6 +56,7 @@ class AppField extends StatelessWidget {
5856
obscureText: obscureText,
5957
minLines: minLines,
6058
maxLines: maxLines,
59+
autofillHints: autofillHints,
6160
style: Theme.of(context).textTheme.bodyMedium,
6261
decoration: InputDecoration(
6362
contentPadding: padding ?? const EdgeInsets.all(16.0),
@@ -73,6 +72,8 @@ class AppField extends StatelessWidget {
7372
),
7473
prefixIcon: prefix,
7574
suffixIcon: suffix,
75+
fillColor: color ?? Theme.of(context).inputDecorationTheme.fillColor,
76+
filled: true,
7677
border: OutlineInputBorder(
7778
borderRadius: BorderRadius.circular(10.0),
7879
borderSide: BorderSide(

lib/views/authentication/login_form_view.dart

Lines changed: 127 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter/services.dart';
23
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
34
import 'package:piwigo_ng/app.dart';
45
import 'package:piwigo_ng/components/buttons/animated_piwigo_button.dart';
@@ -131,7 +132,7 @@ class _LoginFormViewState extends State<LoginFormView> {
131132
}
132133

133134
void _onLogin() async {
134-
if (_urlError) return;
135+
// if (_urlError) return;
135136
App.scaffoldMessengerKey.currentState?.clearSnackBars();
136137
_btnController.start();
137138
try {
@@ -143,6 +144,7 @@ class _LoginFormViewState extends State<LoginFormView> {
143144
if (result.data == false || result.error != null) {
144145
_onLoginError(result);
145146
} else {
147+
TextInput.finishAutofillContext();
146148
await _onLoginSuccess(result);
147149
}
148150
} on Error catch (e) {
@@ -160,129 +162,137 @@ class _LoginFormViewState extends State<LoginFormView> {
160162

161163
@override
162164
Widget build(BuildContext context) {
163-
return Column(
164-
crossAxisAlignment: CrossAxisAlignment.stretch,
165-
children: [
166-
AppField(
167-
key: _urlKey,
168-
margin: const EdgeInsets.symmetric(vertical: 4.0),
169-
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
170-
controller: _urlController,
171-
onChanged: (value) {
172-
bool isError = !_urlValidator(value);
173-
if (_urlError != isError) {
174-
_urlError = isError;
175-
}
176-
setState(() {
177-
_url = value;
178-
});
179-
},
180-
textInputAction: TextInputAction.next,
181-
prefix: _securedPrefix,
182-
hint: 'example.piwigo.com',
183-
error: _urlError,
184-
),
185-
AppField(
186-
margin: const EdgeInsets.symmetric(vertical: 4.0),
187-
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
188-
controller: _usernameController,
189-
onChanged: (value) {
190-
if (_idError) {
191-
_idError = false;
192-
}
193-
setState(() {
194-
_username = value;
195-
});
196-
},
197-
textInputAction: TextInputAction.next,
198-
hint: "username",
199-
error: _idError,
200-
prefix: const Icon(Icons.person),
201-
suffix: _username.isNotEmpty
202-
? GestureDetector(
203-
behavior: HitTestBehavior.opaque,
204-
onTap: () => setState(() {
205-
_usernameController.clear();
206-
_username = '';
207-
}),
208-
child: Padding(
209-
padding: const EdgeInsets.all(8.0),
210-
child: Icon(
211-
Icons.clear,
212-
color: Theme.of(context).colorScheme.secondary,
165+
return AutofillGroup(
166+
child: Column(
167+
crossAxisAlignment: CrossAxisAlignment.stretch,
168+
children: [
169+
AppField(
170+
key: _urlKey,
171+
margin: const EdgeInsets.symmetric(vertical: 4.0),
172+
padding:
173+
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
174+
controller: _urlController,
175+
onChanged: (value) {
176+
bool isError = !_urlValidator(value);
177+
if (_urlError != isError) {
178+
_urlError = isError;
179+
}
180+
setState(() {
181+
_url = value;
182+
});
183+
},
184+
textInputAction: TextInputAction.next,
185+
autofillHints: [AutofillHints.url],
186+
prefix: _securedPrefix,
187+
hint: 'example.piwigo.com',
188+
error: _urlError,
189+
),
190+
AppField(
191+
margin: const EdgeInsets.symmetric(vertical: 4.0),
192+
padding:
193+
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
194+
controller: _usernameController,
195+
onChanged: (value) {
196+
if (_idError) {
197+
_idError = false;
198+
}
199+
setState(() {
200+
_username = value;
201+
});
202+
},
203+
textInputAction: TextInputAction.next,
204+
autofillHints: [AutofillHints.username],
205+
hint: appStrings.login_userPlaceholder,
206+
error: _idError,
207+
prefix: const Icon(Icons.person),
208+
suffix: _username.isNotEmpty
209+
? GestureDetector(
210+
behavior: HitTestBehavior.opaque,
211+
onTap: () => setState(() {
212+
_usernameController.clear();
213+
_username = '';
214+
}),
215+
child: Padding(
216+
padding: const EdgeInsets.all(8.0),
217+
child: Icon(
218+
Icons.clear,
219+
color: Theme.of(context).colorScheme.secondary,
220+
),
213221
),
214-
),
215-
)
216-
: null,
217-
),
218-
AppField(
219-
margin: const EdgeInsets.symmetric(vertical: 4.0),
220-
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
221-
controller: _passwordController,
222-
onFieldSubmitted: (String value) {
223-
FocusScope.of(context).unfocus();
224-
_onLogin();
225-
},
226-
onChanged: (value) {
227-
if (_idError) {
228-
_idError = false;
229-
}
230-
setState(() {
231-
_password = value;
232-
});
233-
},
234-
textInputAction: TextInputAction.done,
235-
obscureText: !_showPassword,
236-
hint: "password",
237-
error: _idError,
238-
prefix: GestureDetector(
239-
onTap: () => setState(() {
240-
_showPassword = !_showPassword;
241-
}),
242-
child: Icon(_showPassword ? Icons.lock_open : Icons.lock),
222+
)
223+
: null,
243224
),
244-
suffix: _password.isNotEmpty
245-
? GestureDetector(
246-
behavior: HitTestBehavior.opaque,
247-
onTap: () => setState(() {
248-
_passwordController.clear();
249-
_password = '';
250-
}),
251-
child: Padding(
252-
padding: const EdgeInsets.all(8.0),
253-
child: Icon(
254-
Icons.clear,
255-
color: Theme.of(context).colorScheme.secondary,
225+
AppField(
226+
margin: const EdgeInsets.symmetric(vertical: 4.0),
227+
padding:
228+
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
229+
controller: _passwordController,
230+
onFieldSubmitted: (String value) {
231+
FocusScope.of(context).unfocus();
232+
_onLogin();
233+
},
234+
onChanged: (value) {
235+
if (_idError) {
236+
_idError = false;
237+
}
238+
setState(() {
239+
_password = value;
240+
});
241+
},
242+
textInputAction: TextInputAction.done,
243+
autofillHints: [AutofillHints.password],
244+
obscureText: !_showPassword,
245+
hint: appStrings.login_passwordPlaceholder,
246+
error: _idError,
247+
prefix: GestureDetector(
248+
onTap: () => setState(() {
249+
_showPassword = !_showPassword;
250+
}),
251+
child: Icon(_showPassword ? Icons.lock_open : Icons.lock),
252+
),
253+
suffix: _password.isNotEmpty
254+
? GestureDetector(
255+
behavior: HitTestBehavior.opaque,
256+
onTap: () => setState(() {
257+
_passwordController.clear();
258+
_password = '';
259+
}),
260+
child: Padding(
261+
padding: const EdgeInsets.all(8.0),
262+
child: Icon(
263+
Icons.clear,
264+
color: Theme.of(context).colorScheme.secondary,
265+
),
256266
),
257-
),
258-
)
259-
: null,
260-
),
261-
Padding(
262-
padding: const EdgeInsets.only(top: 12.0),
263-
child: AnimatedPiwigoButton(
264-
controller: _btnController,
265-
// disabled: _urlError,
266-
color: Theme.of(context).primaryColor,
267-
onPressed: _onLogin,
268-
child: Text(
269-
appStrings.login,
270-
style: Theme.of(context).textTheme.displaySmall,
267+
)
268+
: null,
269+
),
270+
Padding(
271+
padding: const EdgeInsets.only(top: 12.0),
272+
child: AnimatedPiwigoButton(
273+
controller: _btnController,
274+
// disabled: _urlError,
275+
color: Theme.of(context).primaryColor,
276+
onPressed: _onLogin,
277+
child: Text(
278+
appStrings.login,
279+
style: Theme.of(context).textTheme.displaySmall,
280+
),
271281
),
272282
),
273-
),
274-
TextButton(
275-
style: ButtonStyle(
276-
foregroundColor: MaterialStateProperty.resolveWith(
277-
(states) => Theme.of(context).colorScheme.secondary,
283+
TextButton(
284+
style: ButtonStyle(
285+
foregroundColor: MaterialStateProperty.resolveWith(
286+
(states) => Theme.of(context).colorScheme.secondary,
287+
),
278288
),
289+
onPressed: () {
290+
Navigator.of(context).pushNamed(LoginSettingsPage.routeName);
291+
},
292+
child: Text(appStrings.login_advancedParameters),
279293
),
280-
onPressed: () {
281-
Navigator.of(context).pushNamed(LoginSettingsPage.routeName);
282-
},
283-
child: Text(appStrings.login_advancedParameters),
284-
),
285-
],
294+
],
295+
),
286296
);
287297
}
288298

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies:
3434
photo_view: ^0.14.0 # Zoom on fullscreen photos
3535
extended_text: ^11.0.0 # Text overflow on left side
3636
flutter_easyloading: ^3.0.5 # Show loading dialog
37+
flutter_html: ^3.0.0-beta.2 # Parse and display HTML elements
3738

3839
# Storage
3940
package_info_plus: ^3.1.2 # Get project info (version)

0 commit comments

Comments
 (0)