feat(password): Add the abiltity for the user to change its password

This commit is contained in:
Jacques 2024-02-01 11:40:49 +01:00
parent 661a3fc331
commit e98fb1bdd8
13 changed files with 410 additions and 155 deletions

View file

@ -1,3 +1,7 @@
## 1.2.0
- Added the posibilty to enable the user to change it's password.
## 1.1.6
- Fixed avatar image zooming when constrained beyond it's size

View file

@ -2,8 +2,6 @@
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:typed_data';
import 'package:example/utils/example_profile_service.dart';
import 'package:flutter/material.dart';
@ -44,9 +42,6 @@ class _ProfileExampleState extends State<ProfileExample> {
_user = User(
firstName: 'Firstname',
lastName: 'Lastname',
image: Uint8List.fromList(
[],
),
profileData: profileData,
);
}
@ -58,14 +53,9 @@ class _ProfileExampleState extends State<ProfileExample> {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(
height: 400,
width: 800,
child: ProfilePage(
showItems: false,
prioritizedItems: const ['remarks', 'about'],
changePasswordConfig:
const ChangePasswordConfig(enablePasswordChange: true),
wrapViewOptions: WrapViewOptions(
direction: Axis.horizontal,
spacing: 16,
@ -93,6 +83,20 @@ class _ProfileExampleState extends State<ProfileExample> {
return null;
},
},
inputDecorationField: {
'password_1': const InputDecoration(
constraints: BoxConstraints(
maxHeight: 60,
maxWidth: 200,
),
),
'password_2': const InputDecoration(
constraints: BoxConstraints(
maxHeight: 60,
maxWidth: 200,
),
),
},
),
user: _user,
service: ExampleProfileService(),
@ -107,10 +111,6 @@ class _ProfileExampleState extends State<ProfileExample> {
),
),
),
const Text('test')
],
),
),
);
}
}

View file

@ -28,4 +28,9 @@ class ExampleProfileService extends ProfileService {
{required Function(bool isUploading) onUploadStateChanged}) {
debugPrint('Updating avatar');
}
@override
FutureOr<void> changePassword(String password) {
debugPrint(password);
}
}

View file

@ -21,26 +21,26 @@ packages:
dependency: transitive
description:
name: cached_network_image
sha256: be69fd8b429d48807102cee6ab4106a55e1f2f3e79a1b83abb8572bc06c64f26
sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
version: "3.3.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7
sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
version: "4.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "1.1.1"
characters:
dependency: transitive
description:
@ -61,10 +61,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.2"
version: "1.18.0"
crypto:
dependency: transitive
description:
@ -110,22 +110,23 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_blurhash:
dependency: transitive
description:
name: flutter_blurhash
sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3"
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "3.3.1"
flutter_input_library:
dependency: transitive
description:
path: "."
ref: "2.7.0"
resolved-ref: "8eb1d80a9f08be0b7fe70078104d1a8851083edd"
url: "https://github.com/Iconica-Development/flutter_input_library"
source: git
version: "2.7.0"
flutter_lints:
dependency: "direct dev"
description:
@ -140,7 +141,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.0.5"
version: "1.1.6"
flutter_test:
dependency: "direct dev"
description: flutter
@ -162,6 +163,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
intl:
dependency: transitive
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
lints:
dependency: transitive
description:
@ -190,18 +199,18 @@ packages:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143"
sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "2.0.0"
path:
dependency: transitive
description:
@ -266,14 +275,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.3"
pedantic:
dependency: transitive
description:
name: pedantic
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
platform:
dependency: transitive
description:
@ -339,18 +340,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -379,10 +380,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.6.1"
typed_data:
dependency: transitive
description:
@ -411,10 +412,10 @@ packages:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
version: "0.3.0"
win32:
dependency: transitive
description:
@ -432,5 +433,5 @@ packages:
source: hosted
version: "0.2.0+2"
sdks:
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.3.0"
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.10.0"

View file

@ -11,4 +11,5 @@ export 'src/widgets/avatar/avatar.dart';
export 'src/services/profile_service.dart';
export 'src/widgets/item_builder/item_builder.dart';
export 'src/models/user.dart';
export 'src/models/change_password_config.dart';
export 'src/widgets/item_builder/item_builder_options.dart';

View file

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
/// Configuration to enable to user to change his password in the profilescreen.
class ChangePasswordConfig {
const ChangePasswordConfig({
required this.enablePasswordChange,
this.title = 'Change password',
this.titleStyle,
this.underTitle =
'You van make the password more secure using upper and lower case '
'letter, numbers and special characters.',
this.underTitleStyle,
this.saveButtonBuilder,
this.fieldRequiredErrorText = 'Field required',
this.notEqualErrorText = 'Password have to be equal',
});
/// Enables the textfields for the user to provide a new password.
final bool enablePasswordChange;
/// Text for the title above the textfields.
final String title;
/// Textstyle of the title.
final TextStyle? titleStyle;
/// Text for the undertitle just above the textfields.
final String underTitle;
/// Textstyle for the undertitle
final TextStyle? underTitleStyle;
/// Ability to override the standard 'save password' button.
final Widget Function(
BuildContext context,
void Function() onTap,
)? saveButtonBuilder;
/// Error text to be shown when either of the textfields is empty.
final String fieldRequiredErrorText;
/// Error text to be shown when the second password isn't equal to the first password.
final String notEqualErrorText;
}

View file

@ -24,4 +24,6 @@ abstract class ProfileService {
BuildContext context, {
required Function(bool isUploading) onUploadStateChanged,
});
FutureOr<void> changePassword(String password);
}

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_input_library/flutter_input_library.dart';
import 'package:flutter_profile/src/widgets/item_builder/item_builder_options.dart';
/// ItemBuilder is used to set the standard textfield for each undefined users data item.
@ -22,10 +23,9 @@ class ItemBuilder {
text: '${value ?? ''}',
);
late InputDecoration inputDecoration;
inputDecoration =
var inputDecoration =
options.inputDecorationField?[key] ?? options.inputDecoration;
var formFieldKey = GlobalKey<FormFieldState>();
return TextFormField(
style: options.inputTextStyle,
@ -47,4 +47,22 @@ class ItemBuilder {
}
return widget;
}
Widget buildPassword(
String key,
Function(String?) onChanged,
String? Function(String?) validator,
) {
var inputDecoration =
options.inputDecorationField?[key] ?? options.inputDecoration;
return FlutterFormInputPassword(
style: options.inputTextStyle,
decoration: inputDecoration,
onChanged: onChanged,
enabled: !options.readOnly,
validator: (value) =>
validator(value) ?? options.validators?[key]?.call(value),
);
}
}

View file

@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
import 'package:flutter_profile/flutter_profile.dart';
class ChangePassword extends StatefulWidget {
const ChangePassword({
required this.config,
required this.service,
this.wrapViewOptions,
this.wrapItemsBuilder,
this.itemBuilder,
this.itemBuilderOptions,
this.style = const ProfileStyle(),
super.key,
});
final ChangePasswordConfig config;
final WrapViewOptions? wrapViewOptions;
final ItemBuilder? itemBuilder;
final ItemBuilderOptions? itemBuilderOptions;
final Widget Function(BuildContext context, Widget child)? wrapItemsBuilder;
final ProfileStyle style;
final ProfileService service;
@override
State<ChangePassword> createState() => _ChangePasswordState();
}
class _ChangePasswordState extends State<ChangePassword> {
late var config = widget.config;
late final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late final Widget? changePasswordChild;
String? password1;
String? password2;
late var builder = widget.itemBuilder ??
ItemBuilder(
options: widget.itemBuilderOptions ?? ItemBuilderOptions(),
);
@override
void initState() {
super.initState();
var changePasswordItems = Wrap(
alignment: widget.wrapViewOptions?.wrapAlignment ?? WrapAlignment.start,
direction: widget.wrapViewOptions?.direction ?? Axis.vertical,
spacing: widget.wrapViewOptions?.spacing ?? 0,
runSpacing: widget.wrapViewOptions?.runSpacing ?? 0,
clipBehavior: widget.wrapViewOptions?.clipBehavior ?? Clip.none,
children: [
builder.buildPassword(
'password_1',
(value) => password1 = value,
(value) {
if (password1?.isEmpty ?? true) {
return config.fieldRequiredErrorText;
}
return null;
},
),
builder.buildPassword(
'password_2',
(value) => password2 = value,
(value) {
if (password2?.isEmpty ?? true) {
return config.fieldRequiredErrorText;
}
if (password2 != password1) {
return config.notEqualErrorText;
}
return null;
},
),
],
);
changePasswordChild =
widget.wrapItemsBuilder?.call(context, changePasswordItems) ??
changePasswordItems;
}
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
void onTapSave() {
if ((_formKey.currentState?.validate() ?? false) && password2 != null) {
widget.service.changePassword(password2!);
}
}
return Form(
key: _formKey,
child: Column(
children: [
SizedBox(
height: widget.style.betweenDefaultItemPadding * 2.5,
),
Text(
config.title,
style: config.titleStyle ?? theme.textTheme.titleMedium,
textAlign: TextAlign.center,
),
Text(
config.underTitle,
style: config.underTitleStyle ?? theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
),
SizedBox(
height: widget.style.betweenDefaultItemPadding,
),
changePasswordChild!,
SizedBox(
height: widget.style.betweenDefaultItemPadding * 2,
),
config.saveButtonBuilder?.call(
context,
() => onTapSave(),
) ??
FilledButton(
onPressed: () => onTapSave(),
child: const Text('Save password'),
),
const Spacer(),
],
),
);
}
}

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_profile/src/models/change_password_config.dart';
import 'package:flutter_profile/src/models/user.dart';
import 'package:flutter_profile/src/services/profile_service.dart';
import 'package:flutter_profile/src/widgets/item_builder/item_builder.dart';
@ -46,6 +47,8 @@ class ProfilePage extends StatefulWidget {
this.wrapViewOptions,
this.extraWidgets,
this.formKey,
this.changePasswordConfig =
const ChangePasswordConfig(enablePasswordChange: false),
}) : super(key: key);
/// User containing all the user data.
@ -96,6 +99,9 @@ class ProfilePage extends StatefulWidget {
/// Use the form key to save on any custom callback
final GlobalKey<FormState>? formKey;
/// Configuration to give the user the option to change his/her password.
final ChangePasswordConfig changePasswordConfig;
@override
State<ProfilePage> createState() => _ProfilePageState();
}
@ -123,6 +129,7 @@ class _ProfilePageState extends State<ProfilePage> {
extraWidgets: widget.extraWidgets,
formKey: widget.formKey,
avatarBackgroundColor: widget.avatarBackgroundColor,
changePasswordConfig: widget.changePasswordConfig,
);
}
}

View file

@ -3,12 +3,14 @@
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_profile/src/models/change_password_config.dart';
import 'package:flutter_profile/src/models/user.dart';
import 'package:flutter_profile/src/services/profile_service.dart';
import 'package:flutter_profile/src/widgets/avatar/avatar_wrapper.dart';
import 'package:flutter_profile/src/widgets/item_builder/item_builder.dart';
import 'package:flutter_profile/src/widgets/item_builder/item_builder_options.dart';
import 'package:flutter_profile/src/widgets/item_builder/item_list.dart';
import 'package:flutter_profile/src/widgets/profile/change_password_widget.dart';
import 'package:flutter_profile/src/widgets/profile/profile_page.dart';
import 'package:flutter_profile/src/widgets/profile/profile_style.dart';
@ -31,6 +33,8 @@ class ProfileWrapper extends StatefulWidget {
this.wrapItemsBuilder,
this.formKey,
this.extraWidgets,
this.changePasswordConfig =
const ChangePasswordConfig(enablePasswordChange: false),
super.key,
});
@ -50,6 +54,7 @@ class ProfileWrapper extends StatefulWidget {
final Widget Function(BuildContext context, Widget child)? wrapItemsBuilder;
final Map<String, Widget>? extraWidgets;
final GlobalKey<FormState>? formKey;
final ChangePasswordConfig changePasswordConfig;
/// Map keys of items that should be shown first before the default items and the rest of the items.
final List<String> prioritizedItems;
@ -193,6 +198,9 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Padding(
padding: widget.style.pagePadding,
child: Column(
@ -229,11 +237,26 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
),
],
if (widget.showItems) Form(key: _formKey, child: child),
if (widget.changePasswordConfig.enablePasswordChange) ...[
Expanded(
child: ChangePassword(
config: widget.changePasswordConfig,
service: widget.service,
wrapViewOptions: widget.wrapViewOptions,
wrapItemsBuilder: widget.wrapItemsBuilder,
itemBuilder: widget.itemBuilder,
itemBuilderOptions: widget.itemBuilderOptions,
style: widget.style,
),
),
],
if (widget.bottomActionText != null) ...[
SizedBox(
height: widget.style.betweenDefaultItemPadding,
),
if (!widget.changePasswordConfig.enablePasswordChange) ...[
const Spacer(),
],
InkWell(
onTap: () {
widget.service.pageBottomAction();
@ -246,10 +269,14 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
),
),
),
] else ...[
],
if (widget.bottomActionText == null &&
!widget.changePasswordConfig.enablePasswordChange) ...[
const Spacer(),
]
],
],
),
),
),
),
);

View file

@ -1,14 +1,22 @@
name: flutter_profile
description: Flutter profile package
version: 1.1.6
version: 1.2.0
repository: https://github.com/Iconica-Development/flutter_profile
publish_to: none
environment:
sdk: ">=2.17.6 <3.0.0"
sdk: ^3.0.0
flutter: ">=1.17.0"
dependencies:
cached_network_image: ^3.2.2
cached_network_image: ^3.3.0
flutter_input_library:
git:
url: https://github.com/Iconica-Development/flutter_input_library
ref: 2.7.0
flutter:
sdk: flutter

View file

@ -25,4 +25,7 @@ class TestProfileService extends ProfileService {
BuildContext context, {
required Function(bool isUploading) onUploadStateChanged,
}) {}
@override
FutureOr<void> changePassword(String password) {}
}