mirror of
https://github.com/Iconica-Development/flutter_profile.git
synced 2025-05-19 01:03:45 +02:00
commit
efbcdaac21
23 changed files with 753 additions and 403 deletions
41
README.md
41
README.md
|
@ -1,29 +1,38 @@
|
||||||
[](https://github.com/tenhobi/effective_dart)
|
# Flutter Profile
|
||||||
|
|
||||||
Short description of what your package is, why you created it. What issues it fixes and how it works. Also mention the available platforms
|
Flutter Profile is a package you can use to display any user data and let them alter it if desired.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Display every type of user data.
|
||||||
|
Display an image/avatar.
|
||||||
|
Enable the user to alter his data or withhold the user from doing so.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
What setup steps are neccesarry and why>
|
To use this package, add `flutter_profile` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).
|
||||||
|
|
||||||
<details>
|
## How To Use
|
||||||
<summary>PLATFORM</summary>
|
|
||||||
|
|
||||||
specific platform steps
|
|
||||||
|
|
||||||
</details>
|
See the [Example Code](example/lib/main.dart) for an example on how to use this package.
|
||||||
|
|
||||||
## How to use
|
Underneath are all paramters, of the 'ProfilePage' widget, listed with an explanation.
|
||||||
|
|
||||||
How can we use the package descibe the most common ways with examples in
|
| Parameter | Explaination |
|
||||||
|
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
```dart
|
| user | The class that contains all the user data. |
|
||||||
codeblocks
|
| service | The service which determines what happens the user wants to update their profile, update their avatar or press the InkWell at the bottom of the page. |
|
||||||
```
|
| style | With the use of ProfileStyle a couple of style options can be set for the form. |
|
||||||
|
| customAvatar | An option to override the standard avatar. |
|
||||||
|
| showAvatar | The ability to disable/enable the avatar. |
|
||||||
|
| itemBuilder | The way to override the standard textfield for each standard piece of user data. |
|
||||||
|
| itemBuilderOptions | The options used by the standard itemBuilder to alter the function and style of the textfields |
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
Please file any issues, bugs or feature request as an issue on our [GitHub](REPO URL) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
|
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_profile/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
|
||||||
|
|
||||||
## Want to contribute
|
## Want to contribute
|
||||||
|
|
||||||
|
@ -31,4 +40,4 @@ If you would like to contribute to the plugin (e.g. by improving the documentati
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
This flutter_profile package for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>
|
This `flutter-image-picker` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>
|
||||||
|
|
BIN
example/image/example_profile.png
Normal file
BIN
example/image/example_profile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
|
@ -1,29 +1,44 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:example/utils/example_profile_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:profile/profile.dart';
|
import 'package:flutter_profile/flutter_profile.dart';
|
||||||
|
|
||||||
|
import 'utils/example_profile_data.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({Key? key}) : super(key: key);
|
const MyApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MyApp> createState() => _MyAppState();
|
Widget build(BuildContext context) {
|
||||||
|
return const MaterialApp(
|
||||||
|
home: ProfileExample(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
class ProfileExample extends StatefulWidget {
|
||||||
|
const ProfileExample({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProfileExample> createState() => _ProfileExampleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProfileExampleState extends State<ProfileExample> {
|
||||||
late User _user;
|
late User _user;
|
||||||
MyProfileData profileData = MyProfileData();
|
ProfileData profileData =
|
||||||
|
ExampleProfileData().fromMap({'email': 'example@email.com'});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_user = User(
|
_user = User(
|
||||||
'firstName',
|
'Firstname',
|
||||||
'lastName',
|
'Lastname',
|
||||||
Uint8List.fromList(
|
Uint8List.fromList(
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
|
@ -33,125 +48,56 @@ class _MyAppState extends State<MyApp> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return Scaffold(
|
||||||
theme: ThemeData(
|
body: ProfilePage(
|
||||||
primarySwatch: Colors.blue,
|
bottomActionText: 'Log out',
|
||||||
),
|
itemBuilderOptions: ItemBuilderOptions(
|
||||||
home: ProfilePage(
|
inputDecorationField: {
|
||||||
service: MyProfileService(),
|
'firstName': const InputDecoration(
|
||||||
|
label: Text('First name'),
|
||||||
|
),
|
||||||
|
'lastName': const InputDecoration(
|
||||||
|
label: Text('Last name'),
|
||||||
|
),
|
||||||
|
'email': const InputDecoration(
|
||||||
|
label: Text('E-mail'),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
validators: {
|
||||||
|
'firstName': (String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Field empty';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
'lastName': (String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Field empty';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
'email': (String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Field empty';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
user: _user,
|
user: _user,
|
||||||
),
|
service: ExampleProfileService(),
|
||||||
);
|
style: ProfileStyle(
|
||||||
}
|
avatarStyle: const AvatarStyle(
|
||||||
}
|
displayNameStyle: TextStyle(fontSize: 20),
|
||||||
|
),
|
||||||
class MyProfileService extends ProfileService {
|
pagePadding: EdgeInsets.only(
|
||||||
@override
|
top: 50,
|
||||||
deleteProfile() {
|
bottom: 50,
|
||||||
return super.deleteProfile();
|
left: MediaQuery.of(context).size.width * 0.35,
|
||||||
}
|
right: MediaQuery.of(context).size.width * 0.35,
|
||||||
|
),
|
||||||
@override
|
|
||||||
editProfile<T extends ProfileData>(
|
|
||||||
User<ProfileData> user, String key, String value) {
|
|
||||||
return super.editProfile(user, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
uploadImage() {
|
|
||||||
return super.uploadImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyProfileData extends ProfileData {
|
|
||||||
MyProfileData({
|
|
||||||
this.justMyNumber = '1',
|
|
||||||
this.justMyString = 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String justMyNumber;
|
|
||||||
int justMyString;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> mapWidget(Function update) {
|
|
||||||
return {
|
|
||||||
'justMyString': Container(
|
|
||||||
height: 100,
|
|
||||||
width: 300,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
primary: justMyString == 1 ? Colors.green : Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
justMyString = 1;
|
|
||||||
update();
|
|
||||||
print(justMyString);
|
|
||||||
},
|
|
||||||
child: const Text('1'),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
primary: justMyString == 2 ? Colors.green : Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
justMyString = 2;
|
|
||||||
update();
|
|
||||||
print(justMyString);
|
|
||||||
},
|
|
||||||
child: const Text('2'),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
primary: justMyString == 3 ? Colors.green : Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
justMyString = 3;
|
|
||||||
update();
|
|
||||||
},
|
|
||||||
child: const Text('3'),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
primary: justMyString == 4 ? Colors.green : Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
justMyString = 4;
|
|
||||||
update();
|
|
||||||
},
|
|
||||||
child: const Text('4'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'justMyNumber': null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
ProfileData fromMap(Map<String, dynamic> data) {
|
|
||||||
return MyProfileData(
|
|
||||||
justMyNumber: data['justMyNumber'],
|
|
||||||
justMyString: int.parse(
|
|
||||||
data['justMyString'].toString(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return {
|
|
||||||
'justMyNumber': justMyNumber,
|
|
||||||
'justMyString': justMyString,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
ProfileData create() {
|
|
||||||
return MyProfileData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
39
example/lib/utils/example_profile_data.dart
Normal file
39
example/lib/utils/example_profile_data.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_profile/flutter_profile.dart';
|
||||||
|
|
||||||
|
class ExampleProfileData extends ProfileData {
|
||||||
|
ExampleProfileData({
|
||||||
|
this.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? email;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> mapWidget(
|
||||||
|
VoidCallback update,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
'email': null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProfileData fromMap(Map<String, dynamic> data) {
|
||||||
|
return ExampleProfileData(
|
||||||
|
email: data['email'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'email': email,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProfileData create() {
|
||||||
|
return ExampleProfileData();
|
||||||
|
}
|
||||||
|
}
|
25
example/lib/utils/example_profile_service.dart
Normal file
25
example/lib/utils/example_profile_service.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_profile/flutter_profile.dart';
|
||||||
|
|
||||||
|
class ExampleProfileService extends ProfileService {
|
||||||
|
ExampleProfileService();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pageBottomAction() {
|
||||||
|
debugPrint('Bottom page action');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void editProfile(
|
||||||
|
User user,
|
||||||
|
String key,
|
||||||
|
String value,
|
||||||
|
) {
|
||||||
|
debugPrint('Editing key: $key with $value');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> uploadImage(BuildContext context) async {
|
||||||
|
debugPrint('Updating avatar');
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ packages:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.2"
|
version: "2.9.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -21,21 +21,14 @@ packages:
|
||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
charcode:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: charcode
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.1"
|
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -56,7 +49,7 @@ packages:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -69,6 +62,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
|
flutter_profile:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: ".."
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -87,35 +87,28 @@ packages:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.11"
|
version: "0.12.12"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4"
|
version: "0.1.5"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.1"
|
version: "1.8.2"
|
||||||
profile:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: ".."
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "0.0.1"
|
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -127,7 +120,7 @@ packages:
|
||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.9.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -148,21 +141,21 @@ packages:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.9"
|
version: "0.4.12"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -34,7 +34,7 @@ dependencies:
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
profile:
|
flutter_profile:
|
||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
9
lib/flutter_profile.dart
Normal file
9
lib/flutter_profile.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
library flutter_profile;
|
||||||
|
|
||||||
|
export 'src/widgets/profile/profile_page.dart';
|
||||||
|
export 'src/widgets/profile/profile_style.dart';
|
||||||
|
export 'src/widgets/avatar/avatar_style.dart';
|
||||||
|
export 'src/services/profile_service.dart';
|
||||||
|
export 'src/widgets/item_builder/item_builder.dart';
|
||||||
|
export 'src/models/user.dart';
|
||||||
|
export 'src/widgets/item_builder/item_builder_options.dart';
|
|
@ -1,6 +0,0 @@
|
||||||
library profile;
|
|
||||||
|
|
||||||
export 'src/widgets/profile/profile_page.dart';
|
|
||||||
export 'src/services/profile_service.dart';
|
|
||||||
export 'src/widgets/item_builder/item_builder.dart';
|
|
||||||
export 'src/models/user.dart';
|
|
|
@ -1,15 +1,15 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:profile/profile.dart';
|
|
||||||
import 'package:profile/src/widgets/item_builder/item_builder.dart';
|
|
||||||
import 'package:profile/src/widgets/item_builder/item_builder_options.dart';
|
|
||||||
|
|
||||||
class User<T extends ProfileData> {
|
/// User is used to contain all user data. It consists of three standard fields: firstName, lastName and image.
|
||||||
|
///
|
||||||
|
/// For additional data profileData can be used.
|
||||||
|
class User {
|
||||||
String? firstName;
|
String? firstName;
|
||||||
String? lastName;
|
String? lastName;
|
||||||
Uint8List? image;
|
Uint8List? image;
|
||||||
T? profileData;
|
ProfileData? profileData;
|
||||||
|
|
||||||
User(
|
User(
|
||||||
this.firstName,
|
this.firstName,
|
||||||
|
@ -37,6 +37,11 @@ class User<T extends ProfileData> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ProfileData is used to store custom/addintional data for a user.
|
||||||
|
///
|
||||||
|
/// The MapWidget method is used to bind a [Widget] to one of the keys. This will override the standard textfield.
|
||||||
|
///
|
||||||
|
/// The Builditems method is used to make the list of field to house the user data.
|
||||||
abstract class ProfileData {
|
abstract class ProfileData {
|
||||||
const ProfileData();
|
const ProfileData();
|
||||||
|
|
||||||
|
@ -44,46 +49,7 @@ abstract class ProfileData {
|
||||||
|
|
||||||
Map<String, dynamic> toMap();
|
Map<String, dynamic> toMap();
|
||||||
|
|
||||||
Map<String, dynamic> mapWidget(Function update);
|
Map<String, dynamic> mapWidget(VoidCallback update, BuildContext context);
|
||||||
|
|
||||||
ProfileData create();
|
ProfileData create();
|
||||||
|
|
||||||
List<Widget> buildItems(
|
|
||||||
Map<String, dynamic> items,
|
|
||||||
Map<String, dynamic> typeMap,
|
|
||||||
double spacing,
|
|
||||||
Function(String, String) updateProfile, {
|
|
||||||
ItemBuilder? itemBuilder,
|
|
||||||
ItemBuilderOptions? itemBuilderOptions,
|
|
||||||
}) {
|
|
||||||
var widgets = <Widget>[];
|
|
||||||
ItemBuilder builder = ItemBuilder(
|
|
||||||
options: itemBuilderOptions ?? const ItemBuilderOptions(),
|
|
||||||
);
|
|
||||||
for (var item in items.entries) {
|
|
||||||
itemBuilder == null
|
|
||||||
? widgets.add(
|
|
||||||
builder.build(
|
|
||||||
item.value,
|
|
||||||
typeMap[item.key],
|
|
||||||
(value) {
|
|
||||||
updateProfile(item.key, value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: widgets.add(
|
|
||||||
itemBuilder.build(
|
|
||||||
item.value,
|
|
||||||
typeMap[item.key],
|
|
||||||
(value) {
|
|
||||||
updateProfile(item.key, value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
widgets.add(SizedBox(
|
|
||||||
height: spacing,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return widgets;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
import 'package:profile/profile.dart';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_profile/src/models/user.dart';
|
||||||
|
|
||||||
|
/// ProfileService can be extended and set for the profilePage. The following method can be overriden.
|
||||||
|
///
|
||||||
|
/// BottompageAction is called when the [InkWell] at the bottom of the page is tapped.
|
||||||
|
///
|
||||||
|
/// EditProfile is called when a user changes and submits a standard textfields.
|
||||||
|
///
|
||||||
|
/// UploadImage is called when te user presses the avatar.
|
||||||
abstract class ProfileService {
|
abstract class ProfileService {
|
||||||
const ProfileService();
|
const ProfileService();
|
||||||
|
|
||||||
deleteProfile() {
|
FutureOr<void> pageBottomAction();
|
||||||
print("Request to delete profile");
|
|
||||||
// TODO(anyone) project specific
|
|
||||||
}
|
|
||||||
|
|
||||||
editProfile<T extends ProfileData>(User user, String key, String value) {
|
FutureOr<void> editProfile(User user, String key, String value);
|
||||||
if (user.profileData != null) {
|
|
||||||
var map = user.profileData!.toMap();
|
|
||||||
if (map.containsKey(key)) {
|
|
||||||
map[key] = value;
|
|
||||||
var profile = user.profileData!.create();
|
|
||||||
user.profileData = profile.fromMap(map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadImage() {
|
FutureOr<void> uploadImage(BuildContext context);
|
||||||
print('Request to change picture');
|
|
||||||
// TODO(anyone) open image picker and update profile
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:profile/src/widgets/avatar/avatar_style.dart';
|
import 'package:flutter_profile/src/widgets/avatar/avatar_style.dart';
|
||||||
|
|
||||||
class Avatar extends StatelessWidget {
|
class Avatar extends StatelessWidget {
|
||||||
const Avatar({
|
const Avatar({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.image,
|
this.image,
|
||||||
this.name,
|
this.firstName,
|
||||||
|
this.lastName,
|
||||||
this.avatar,
|
this.avatar,
|
||||||
this.style = const AvatarStyle(),
|
this.style = const AvatarStyle(),
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Uint8List? image;
|
final Uint8List? image;
|
||||||
final String? name;
|
final String? firstName;
|
||||||
|
final String? lastName;
|
||||||
final Widget? avatar;
|
final Widget? avatar;
|
||||||
final AvatarStyle style;
|
final AvatarStyle style;
|
||||||
|
|
||||||
|
@ -22,9 +24,12 @@ class Avatar extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
_avatar(),
|
_avatar(),
|
||||||
if (name != null)
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
if (firstName != null || lastName != null)
|
||||||
Text(
|
Text(
|
||||||
name!,
|
'${firstName ?? ''} ${lastName ?? ''}',
|
||||||
style: style.displayNameStyle,
|
style: style.displayNameStyle,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -39,43 +44,41 @@ class Avatar extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
width: style.width,
|
width: style.width,
|
||||||
height: style.height,
|
height: style.height,
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
|
image: DecorationImage(
|
||||||
|
image: MemoryImage(image!),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Image.memory(image!),
|
|
||||||
);
|
);
|
||||||
} else if (name != null && name!.isNotEmpty) {
|
} else if (firstName != null || lastName != null) {
|
||||||
return Container(
|
return Container(
|
||||||
width: style.width,
|
width: style.width,
|
||||||
height: style.height,
|
height: style.height,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _generateColorWithIntials(name!),
|
color: _generateColorWithIntials(firstName, lastName),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
_getInitials(name!),
|
style: const TextStyle(fontSize: 40),
|
||||||
|
_getInitials(firstName, lastName),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return SizedBox(
|
return Container();
|
||||||
width: style.width,
|
|
||||||
height: style.height,
|
|
||||||
// TODO(anyone) child fallback image
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getInitials(String name) {
|
String _getInitials(String? firstName, String? lastName) {
|
||||||
var nameList = name.split(' ');
|
return (firstName?[0] ?? '') + (lastName?[0] ?? '');
|
||||||
return nameList.first[0] + nameList.last[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _generateColorWithIntials(String name) {
|
Color _generateColorWithIntials(String? firstName, String? lastName) {
|
||||||
var nameList = name.split(' ');
|
var uniqueInitialId = (firstName?.toLowerCase().codeUnitAt(0) ?? 0) +
|
||||||
var uniqueInitialId = nameList.first.toLowerCase().codeUnitAt(0) +
|
(lastName?.toLowerCase().codeUnitAt(0) ?? 0);
|
||||||
nameList.last.toLowerCase().codeUnitAt(0);
|
|
||||||
|
|
||||||
return Colors.primaries[uniqueInitialId % Colors.primaries.length];
|
return Colors.primaries[uniqueInitialId % Colors.primaries.length];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// AvatarStyle is used to set the style of the avatar displayed at the top of the [ProfilePage].
|
||||||
|
///
|
||||||
|
/// Width is used to set the width of the avatar. Defaults to 100.
|
||||||
|
///
|
||||||
|
/// Height is used to set the height of the avatar. Defaults to 100.
|
||||||
|
///
|
||||||
|
/// InitialStyle sets the [TextStyle] of the initial which are shown when no image is provided.
|
||||||
|
///
|
||||||
|
/// DisplayNameStyle sets the [TextStyle] for the displayname underneath the avatar.
|
||||||
class AvatarStyle {
|
class AvatarStyle {
|
||||||
const AvatarStyle({
|
const AvatarStyle({
|
||||||
this.width = 100,
|
this.width = 100,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:profile/src/widgets/item_builder/item_builder_options.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.
|
||||||
|
///
|
||||||
|
/// Options sets options for the textfield.
|
||||||
class ItemBuilder {
|
class ItemBuilder {
|
||||||
ItemBuilder({
|
ItemBuilder({
|
||||||
required this.options,
|
required this.options,
|
||||||
|
@ -8,19 +11,34 @@ class ItemBuilder {
|
||||||
|
|
||||||
final ItemBuilderOptions options;
|
final ItemBuilderOptions options;
|
||||||
|
|
||||||
Widget build(dynamic value, Widget? widget, Function(String) updateItem) {
|
Widget build(String key, GlobalKey<FormState> formKey, dynamic value,
|
||||||
|
Widget? widget, Function(String) updateItem) {
|
||||||
if (widget == null) {
|
if (widget == null) {
|
||||||
var controller = TextEditingController(
|
var controller = TextEditingController(
|
||||||
text: '$value',
|
text: '${value ?? ''}',
|
||||||
);
|
);
|
||||||
|
|
||||||
return TextField(
|
late InputDecoration inputDecoration;
|
||||||
controller: controller,
|
|
||||||
decoration: options.inputDecoration,
|
inputDecoration =
|
||||||
readOnly: options.readOnly,
|
options.inputDecorationField?[key] ?? options.inputDecoration;
|
||||||
onSubmitted: (s) {
|
|
||||||
updateItem(s);
|
return Form(
|
||||||
},
|
key: formKey,
|
||||||
|
child: TextFormField(
|
||||||
|
key: Key(key),
|
||||||
|
controller: controller,
|
||||||
|
decoration: inputDecoration,
|
||||||
|
readOnly: options.readOnly,
|
||||||
|
onFieldSubmitted: (value) {
|
||||||
|
if (formKey.currentState!.validate()) {
|
||||||
|
updateItem(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
return options.validators?[key]?.call(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return widget;
|
return widget;
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// ItemBuilderOptions is a class to store all settings for a field in the profile page.
|
||||||
|
///
|
||||||
|
/// InputDecoration sets the decoration for all standard textfields. This is overridden if a field specific decoration is set by inputDecorationField.
|
||||||
|
///
|
||||||
|
/// inputDecorationField sets the inputdecoration by key of the user data field. So a field can have its own specific decoration.
|
||||||
|
///
|
||||||
|
/// Validator can be used to set a validator for the standard textfield.
|
||||||
class ItemBuilderOptions {
|
class ItemBuilderOptions {
|
||||||
const ItemBuilderOptions({
|
ItemBuilderOptions({
|
||||||
this.inputDecoration = const InputDecoration(),
|
this.inputDecoration = const InputDecoration(),
|
||||||
|
this.inputDecorationField,
|
||||||
this.readOnly = false,
|
this.readOnly = false,
|
||||||
|
this.validators,
|
||||||
});
|
});
|
||||||
|
|
||||||
final InputDecoration inputDecoration;
|
final InputDecoration inputDecoration;
|
||||||
|
final Map<String, InputDecoration>? inputDecorationField;
|
||||||
final bool readOnly;
|
final bool readOnly;
|
||||||
|
final Map<String, String? Function(String?)>? validators;
|
||||||
}
|
}
|
||||||
|
|
80
lib/src/widgets/item_builder/item_list.dart
Normal file
80
lib/src/widgets/item_builder/item_list.dart
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_profile/src/widgets/item_builder/item_builder.dart';
|
||||||
|
import 'package:flutter_profile/src/widgets/item_builder/item_builder_options.dart';
|
||||||
|
|
||||||
|
class ItemList extends StatefulWidget {
|
||||||
|
const ItemList(
|
||||||
|
this.items,
|
||||||
|
this.typeMap,
|
||||||
|
this.spacing,
|
||||||
|
this.updateProfile, {
|
||||||
|
this.itemBuilder,
|
||||||
|
this.itemBuilderOptions,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Map<String, dynamic> items;
|
||||||
|
final Map<String, dynamic> typeMap;
|
||||||
|
final double spacing;
|
||||||
|
final Function(String, String) updateProfile;
|
||||||
|
final ItemBuilder? itemBuilder;
|
||||||
|
final ItemBuilderOptions? itemBuilderOptions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ItemList> createState() => _ItemListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ItemListState extends State<ItemList> {
|
||||||
|
Map<String, GlobalKey<FormState>> formKeys = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
for (var item in widget.items.entries) {
|
||||||
|
formKeys.addAll(
|
||||||
|
{item.key: GlobalKey<FormState>()},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var widgets = <Widget>[];
|
||||||
|
ItemBuilder builder = ItemBuilder(
|
||||||
|
options: widget.itemBuilderOptions ?? ItemBuilderOptions(),
|
||||||
|
);
|
||||||
|
for (var item in widget.items.entries) {
|
||||||
|
widget.itemBuilder == null
|
||||||
|
? widgets.add(
|
||||||
|
builder.build(
|
||||||
|
item.key,
|
||||||
|
formKeys['item.key'] ?? GlobalKey<FormState>(),
|
||||||
|
item.value,
|
||||||
|
widget.typeMap[item.key],
|
||||||
|
(value) {
|
||||||
|
widget.updateProfile(item.key, value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: widgets.add(
|
||||||
|
widget.itemBuilder!.build(
|
||||||
|
item.key,
|
||||||
|
formKeys['item.key'] ?? GlobalKey<FormState>(),
|
||||||
|
item.value,
|
||||||
|
widget.typeMap[item.key],
|
||||||
|
(value) {
|
||||||
|
widget.updateProfile(item.key, value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
widgets.add(SizedBox(
|
||||||
|
height: widget.spacing,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: widgets,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,28 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:profile/profile.dart';
|
import 'package:flutter_profile/src/models/user.dart';
|
||||||
import 'package:profile/src/widgets/avatar/avatar.dart';
|
import 'package:flutter_profile/src/services/profile_service.dart';
|
||||||
import 'package:profile/src/widgets/item_builder/item_builder_options.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:profile/src/widgets/profile/profile_style.dart';
|
import 'package:flutter_profile/src/widgets/profile/profile_style.dart';
|
||||||
|
import 'package:flutter_profile/src/widgets/profile/proifle_wrapper.dart';
|
||||||
|
|
||||||
|
/// The ProfilePage widget is able to show the data of a user. By default the user is able to change this data. The widget has a couple of parameters listed below:
|
||||||
|
///
|
||||||
|
/// User will contain the data of the user which atleast contain a first name, last name and an avatar/image. Besides this information the [ProfileData] can be used to set custom user fields.
|
||||||
|
///
|
||||||
|
/// With the use of the service set by a [ProfileService] some actions can be determined what should occur when the user does the following actions: Deleting/editing the profile or uploading an image.
|
||||||
|
///
|
||||||
|
/// The style can be used the set some style options regarding the whole form. This is done by setting a [ProfileStyle]. The following styling can be set: The style of the avatar, the padding of the page and default padding between items.
|
||||||
|
///
|
||||||
|
/// CustomAvatar can be set to override the standard avatar using any [Widget].
|
||||||
|
///
|
||||||
|
/// ShowAvatar can be set using a [bool] to determine whether the avatar should be shown and be able to be set by the user. Default set to true.
|
||||||
|
///
|
||||||
|
/// BottomActionText sets the text for the inkwell at the bottom of the page. If this is set the null then the [InkWell] is disabled.
|
||||||
|
///
|
||||||
|
/// ItemBuilder is used to determine how the user data is represented.
|
||||||
|
///
|
||||||
|
/// ItemBuilderOptions can be used to just set the settings for fields instead of defining the field itself and how it is used. This field should not be used when the itemBuilder is set.
|
||||||
class ProfilePage extends StatefulWidget {
|
class ProfilePage extends StatefulWidget {
|
||||||
const ProfilePage({
|
const ProfilePage({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -15,17 +33,31 @@ class ProfilePage extends StatefulWidget {
|
||||||
this.showAvatar = true,
|
this.showAvatar = true,
|
||||||
this.itemBuilder,
|
this.itemBuilder,
|
||||||
this.itemBuilderOptions,
|
this.itemBuilderOptions,
|
||||||
this.showDeleteProfile = true,
|
this.bottomActionText,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// User containing all the user data.
|
||||||
final User user;
|
final User user;
|
||||||
|
|
||||||
|
/// The service the determine what should happen when the user takes action.
|
||||||
final ProfileService service;
|
final ProfileService service;
|
||||||
|
|
||||||
|
/// Style to set some general styling parameters fot the whole page.
|
||||||
final ProfileStyle style;
|
final ProfileStyle style;
|
||||||
|
|
||||||
|
/// The way to override the standard avatar is needed.
|
||||||
final Widget? customAvatar;
|
final Widget? customAvatar;
|
||||||
|
|
||||||
|
/// Whether to show the users avatar.
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
final bool showDeleteProfile;
|
|
||||||
|
/// Sets the text for the [InkWell] at the bottom of the profile page. The [InkWell] is disabled when null.
|
||||||
|
final String? bottomActionText;
|
||||||
|
|
||||||
|
/// Itembuilder is used the build each field in the user.
|
||||||
final ItemBuilder? itemBuilder;
|
final ItemBuilder? itemBuilder;
|
||||||
|
|
||||||
|
/// Used to set settings of eacht field in user.
|
||||||
final ItemBuilderOptions? itemBuilderOptions;
|
final ItemBuilderOptions? itemBuilderOptions;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -44,132 +76,10 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
style: widget.style,
|
style: widget.style,
|
||||||
customAvatar: widget.customAvatar,
|
customAvatar: widget.customAvatar,
|
||||||
showAvatar: widget.showAvatar,
|
showAvatar: widget.showAvatar,
|
||||||
showDeleteProfile: widget.showDeleteProfile,
|
bottomActionText: widget.bottomActionText,
|
||||||
itemBuilder: widget.itemBuilder,
|
itemBuilder: widget.itemBuilder,
|
||||||
itemBuilderOptions: widget.itemBuilderOptions,
|
itemBuilderOptions: widget.itemBuilderOptions,
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfileWrapper extends StatefulWidget {
|
|
||||||
const ProfileWrapper({
|
|
||||||
Key? key,
|
|
||||||
required this.user,
|
|
||||||
required this.service,
|
|
||||||
required this.rebuild,
|
|
||||||
this.style = const ProfileStyle(),
|
|
||||||
this.customAvatar,
|
|
||||||
this.showAvatar = true,
|
|
||||||
this.itemBuilder,
|
|
||||||
this.itemBuilderOptions,
|
|
||||||
this.showDeleteProfile = true,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final User user;
|
|
||||||
final ProfileService service;
|
|
||||||
final ProfileStyle style;
|
|
||||||
final Widget? customAvatar;
|
|
||||||
final bool showAvatar;
|
|
||||||
final bool showDeleteProfile;
|
|
||||||
final ItemBuilder? itemBuilder;
|
|
||||||
final Function rebuild;
|
|
||||||
final ItemBuilderOptions? itemBuilderOptions;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ProfileWrapper> createState() => _ProfileWrapperState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ProfileWrapperState extends State<ProfileWrapper> {
|
|
||||||
late List<Widget> profileItems;
|
|
||||||
List<Widget> defaultItems = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
profileItems = widget.user.profileData!.buildItems(
|
|
||||||
widget.user.profileData!.toMap(),
|
|
||||||
widget.user.profileData!.mapWidget(() {
|
|
||||||
widget.rebuild();
|
|
||||||
}),
|
|
||||||
widget.style.betweenDefaultItemPadding,
|
|
||||||
(key, value) {
|
|
||||||
widget.service.editProfile(widget.user, key, value);
|
|
||||||
},
|
|
||||||
itemBuilder: widget.itemBuilder,
|
|
||||||
itemBuilderOptions: widget.itemBuilderOptions,
|
|
||||||
);
|
|
||||||
if (widget.itemBuilder == null) {
|
|
||||||
ItemBuilder builder = ItemBuilder(
|
|
||||||
options: widget.itemBuilderOptions ?? const ItemBuilderOptions(),
|
|
||||||
);
|
|
||||||
defaultItems.add(builder.build(widget.user.firstName, null, (v) {
|
|
||||||
widget.user.firstName = v;
|
|
||||||
}));
|
|
||||||
defaultItems.add(
|
|
||||||
SizedBox(
|
|
||||||
height: widget.style.betweenDefaultItemPadding,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
defaultItems.add(builder.build(widget.user.lastName, null, (v) {
|
|
||||||
widget.user.lastName = v;
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
defaultItems
|
|
||||||
.add(widget.itemBuilder!.build(widget.user.firstName, null, (v) {
|
|
||||||
widget.user.firstName = v;
|
|
||||||
}));
|
|
||||||
defaultItems.add(
|
|
||||||
SizedBox(
|
|
||||||
height: widget.style.betweenDefaultItemPadding,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
defaultItems
|
|
||||||
.add(widget.itemBuilder!.build(widget.user.lastName, null, (v) {
|
|
||||||
widget.user.lastName = v;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Material(
|
|
||||||
child: Padding(
|
|
||||||
padding: widget.style.pagePadding,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (widget.showAvatar)
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
widget.service.uploadImage();
|
|
||||||
},
|
|
||||||
child: Avatar(
|
|
||||||
name: '${widget.user.firstName} ${widget.user.lastName}',
|
|
||||||
style: widget.style.avatarStyle,
|
|
||||||
avatar: widget.customAvatar,
|
|
||||||
image: widget.user.image,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.showAvatar)
|
|
||||||
SizedBox(
|
|
||||||
height: widget.style.betweenDefaultItemPadding,
|
|
||||||
),
|
|
||||||
...defaultItems,
|
|
||||||
...profileItems,
|
|
||||||
if (widget.showDeleteProfile)
|
|
||||||
SizedBox(
|
|
||||||
height: widget.style.betweenDefaultItemPadding,
|
|
||||||
),
|
|
||||||
if (widget.showDeleteProfile)
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
widget.service.deleteProfile();
|
|
||||||
},
|
|
||||||
child: const Text('Profiel verwijderen'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:profile/src/widgets/avatar/avatar_style.dart';
|
import 'package:flutter_profile/src/widgets/avatar/avatar_style.dart';
|
||||||
|
|
||||||
|
/// ProfielStyle is used to set a couple of style paramaters for the whole profile page.
|
||||||
|
///
|
||||||
|
/// AvatarStyle is used to set some styling for the avatar using [AvatarStyle].
|
||||||
|
///
|
||||||
|
/// PagePaddign is used to set the padding around the whole profile page with its parent.
|
||||||
|
///
|
||||||
|
/// BetweenDefaultitemPadding sets te padding between each user data item.
|
||||||
class ProfileStyle {
|
class ProfileStyle {
|
||||||
const ProfileStyle({
|
const ProfileStyle({
|
||||||
this.avatarStyle = const AvatarStyle(),
|
this.avatarStyle = const AvatarStyle(),
|
||||||
|
@ -8,7 +15,12 @@ class ProfileStyle {
|
||||||
this.pagePadding = EdgeInsets.zero,
|
this.pagePadding = EdgeInsets.zero,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// AvatarStyle can be used to set some avatar styling parameters.
|
||||||
final AvatarStyle avatarStyle;
|
final AvatarStyle avatarStyle;
|
||||||
|
|
||||||
|
/// PagePadding can be set to determine the padding of the whole page againt the profile page parent.
|
||||||
final EdgeInsetsGeometry pagePadding;
|
final EdgeInsetsGeometry pagePadding;
|
||||||
|
|
||||||
|
/// BetweenDefaultItemPadding sets the
|
||||||
final double betweenDefaultItemPadding;
|
final double betweenDefaultItemPadding;
|
||||||
}
|
}
|
||||||
|
|
180
lib/src/widgets/profile/proifle_wrapper.dart
Normal file
180
lib/src/widgets/profile/proifle_wrapper.dart
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import 'package:flutter/material.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.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/profile_style.dart';
|
||||||
|
|
||||||
|
class ProfileWrapper extends StatefulWidget {
|
||||||
|
const ProfileWrapper({
|
||||||
|
Key? key,
|
||||||
|
required this.user,
|
||||||
|
required this.service,
|
||||||
|
required this.rebuild,
|
||||||
|
this.style = const ProfileStyle(),
|
||||||
|
this.customAvatar,
|
||||||
|
this.showAvatar = true,
|
||||||
|
this.itemBuilder,
|
||||||
|
this.itemBuilderOptions,
|
||||||
|
this.bottomActionText,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final User user;
|
||||||
|
final ProfileService service;
|
||||||
|
final ProfileStyle style;
|
||||||
|
final Widget? customAvatar;
|
||||||
|
final bool showAvatar;
|
||||||
|
final String? bottomActionText;
|
||||||
|
final ItemBuilder? itemBuilder;
|
||||||
|
final Function rebuild;
|
||||||
|
final ItemBuilderOptions? itemBuilderOptions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProfileWrapper> createState() => _ProfileWrapperState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProfileWrapperState extends State<ProfileWrapper> {
|
||||||
|
List<Widget> defaultItems = [];
|
||||||
|
|
||||||
|
GlobalKey<FormState> firstNameKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
GlobalKey<FormState> lastNameKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
if (widget.itemBuilder == null) {
|
||||||
|
ItemBuilder builder = ItemBuilder(
|
||||||
|
options: widget.itemBuilderOptions ?? ItemBuilderOptions(),
|
||||||
|
);
|
||||||
|
defaultItems.add(
|
||||||
|
builder.build(
|
||||||
|
'firstName',
|
||||||
|
firstNameKey,
|
||||||
|
widget.user.firstName,
|
||||||
|
null,
|
||||||
|
(v) {
|
||||||
|
widget.user.firstName = v;
|
||||||
|
|
||||||
|
widget.service.editProfile(widget.user, 'firstName', v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
defaultItems.add(
|
||||||
|
SizedBox(
|
||||||
|
height: widget.style.betweenDefaultItemPadding,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
defaultItems.add(
|
||||||
|
builder.build(
|
||||||
|
'lastName',
|
||||||
|
lastNameKey,
|
||||||
|
widget.user.lastName,
|
||||||
|
null,
|
||||||
|
(v) {
|
||||||
|
widget.user.lastName = v;
|
||||||
|
|
||||||
|
widget.service.editProfile(widget.user, 'lastName', v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
defaultItems.add(
|
||||||
|
widget.itemBuilder!.build(
|
||||||
|
'firstName',
|
||||||
|
firstNameKey,
|
||||||
|
widget.user.firstName,
|
||||||
|
null,
|
||||||
|
(v) {
|
||||||
|
widget.user.firstName = v;
|
||||||
|
|
||||||
|
widget.service.editProfile(widget.user, 'firstname', v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
defaultItems.add(
|
||||||
|
SizedBox(
|
||||||
|
height: widget.style.betweenDefaultItemPadding,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
defaultItems.add(
|
||||||
|
widget.itemBuilder!.build(
|
||||||
|
'lastName',
|
||||||
|
lastNameKey,
|
||||||
|
widget.user.lastName,
|
||||||
|
null,
|
||||||
|
(v) {
|
||||||
|
widget.user.lastName = v;
|
||||||
|
|
||||||
|
widget.service.editProfile(widget.user, 'lastName', v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Padding(
|
||||||
|
padding: widget.style.pagePadding,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (widget.showAvatar)
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
await widget.service.uploadImage(context);
|
||||||
|
},
|
||||||
|
child: Avatar(
|
||||||
|
firstName: widget.user.firstName,
|
||||||
|
lastName: widget.user.lastName,
|
||||||
|
style: widget.style.avatarStyle,
|
||||||
|
avatar: widget.customAvatar,
|
||||||
|
image: widget.user.image,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.showAvatar)
|
||||||
|
SizedBox(
|
||||||
|
height: widget.style.betweenDefaultItemPadding,
|
||||||
|
),
|
||||||
|
...defaultItems,
|
||||||
|
ItemList(
|
||||||
|
widget.user.profileData!.toMap(),
|
||||||
|
widget.user.profileData!.mapWidget(
|
||||||
|
() {
|
||||||
|
widget.rebuild();
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
widget.style.betweenDefaultItemPadding,
|
||||||
|
(key, value) {
|
||||||
|
widget.service.editProfile(widget.user, key, value);
|
||||||
|
},
|
||||||
|
itemBuilder: widget.itemBuilder,
|
||||||
|
itemBuilderOptions: widget.itemBuilderOptions,
|
||||||
|
),
|
||||||
|
if (widget.bottomActionText != null)
|
||||||
|
SizedBox(
|
||||||
|
height: widget.style.betweenDefaultItemPadding,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (widget.bottomActionText != null)
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.service.pageBottomAction();
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(widget.bottomActionText!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
name: profile
|
name: flutter_profile
|
||||||
description: A new Flutter package project.
|
description: A new Flutter package project.
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
homepage:
|
homepage:
|
||||||
|
|
|
@ -1 +1,94 @@
|
||||||
// todo
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_profile/flutter_profile.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'test_classes/test_profile_data.dart';
|
||||||
|
import 'test_classes/test_profile_service.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Profile page with preset values', (tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: ProfilePage(
|
||||||
|
user: User(
|
||||||
|
'Firstname',
|
||||||
|
'Lastname',
|
||||||
|
Uint8List.fromList([]),
|
||||||
|
TestProfileData(email: 'test@email.com'),
|
||||||
|
),
|
||||||
|
service: TestProfileService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final firstNameFinder = find.text('Firstname');
|
||||||
|
final lastNameFinder = find.text('Lastname');
|
||||||
|
final emailFinder = find.text('test@email.com');
|
||||||
|
|
||||||
|
expect(firstNameFinder, findsOneWidget);
|
||||||
|
expect(lastNameFinder, findsOneWidget);
|
||||||
|
expect(emailFinder, findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Profile page without preset value', (tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: ProfilePage(
|
||||||
|
user: User(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
TestProfileData(email: null),
|
||||||
|
),
|
||||||
|
service: TestProfileService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Profile page with preset value and changing them',
|
||||||
|
(tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: ProfilePage(
|
||||||
|
user: User(
|
||||||
|
'Firstname',
|
||||||
|
'Lastname',
|
||||||
|
null,
|
||||||
|
TestProfileData(email: 'test@email.com'),
|
||||||
|
),
|
||||||
|
service: TestProfileService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.enterText(find.text('Firstname'), 'FirstEditedName');
|
||||||
|
await tester.testTextInput.receiveAction(TextInputAction.send);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await tester.enterText(find.text('test@email.com'), 'edited@emial.com');
|
||||||
|
await tester.testTextInput.receiveAction(TextInputAction.send);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
final firstNameFinder = find.text('Firstname');
|
||||||
|
final firstNameEditedFinder = find.text('FirstEditedName');
|
||||||
|
|
||||||
|
final lastNameFinder = find.text('Lastname');
|
||||||
|
|
||||||
|
final emailFinder = find.text('test@email.com');
|
||||||
|
final emailEditedFinder = find.text('edited@emial.com');
|
||||||
|
|
||||||
|
expect(firstNameFinder, findsNothing);
|
||||||
|
expect(firstNameEditedFinder, findsOneWidget);
|
||||||
|
expect(lastNameFinder, findsOneWidget);
|
||||||
|
expect(emailFinder, findsNothing);
|
||||||
|
expect(emailEditedFinder, findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
39
test/test_classes/test_profile_data.dart
Normal file
39
test/test_classes/test_profile_data.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_profile/flutter_profile.dart';
|
||||||
|
|
||||||
|
class TestProfileData extends ProfileData {
|
||||||
|
TestProfileData({
|
||||||
|
this.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? email;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> mapWidget(
|
||||||
|
VoidCallback update,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
'email': null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProfileData fromMap(Map<String, dynamic> data) {
|
||||||
|
return TestProfileData(
|
||||||
|
email: data['email'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'email': email,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProfileData create() {
|
||||||
|
return TestProfileData();
|
||||||
|
}
|
||||||
|
}
|
19
test/test_classes/test_profile_service.dart
Normal file
19
test/test_classes/test_profile_service.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_profile/flutter_profile.dart';
|
||||||
|
|
||||||
|
class TestProfileService extends ProfileService {
|
||||||
|
TestProfileService();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pageBottomAction() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void editProfile(
|
||||||
|
User user,
|
||||||
|
String key,
|
||||||
|
String value,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> uploadImage(BuildContext context) async {}
|
||||||
|
}
|
Loading…
Reference in a new issue