mirror of
https://github.com/Iconica-Development/flutter_profile.git
synced 2025-05-19 01:03:45 +02:00
Compare commits
21 commits
Author | SHA1 | Date | |
---|---|---|---|
eda3a928cd | |||
|
3c95ad2c3f | ||
8d3be0083a | |||
|
91735f40cd | ||
|
2e0fcb50c0 | ||
|
9374ea9694 | ||
|
d6ab8e4218 | ||
|
6393eda1c1 | ||
|
28eb2b4c8b | ||
|
fe2dd0f169 | ||
|
6b292175ab | ||
|
597b5fa1f2 | ||
|
cdfe8bb405 | ||
|
b787554c63 | ||
|
5517eeef7b | ||
|
5d9a7de763 | ||
|
802265e43c | ||
|
b4d1306d85 | ||
|
ce3c599fa5 | ||
|
c523678e74 | ||
|
579768e240 |
30 changed files with 769 additions and 814 deletions
14
.github/workflows/component-ci.yml
vendored
Normal file
14
.github/workflows/component-ci.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Iconica Standard Component CI Workflow
|
||||
# Workflow Caller version: 2.0.0
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-global-iconica-workflow:
|
||||
uses: Iconica-Development/.github/.github/workflows/component-ci.yml@master
|
||||
secrets: inherit
|
||||
permissions: write-all
|
||||
with:
|
||||
subfolder: "." # add optional subfolder to run workflow in
|
14
.github/workflows/component-documentation.yml
vendored
Normal file
14
.github/workflows/component-documentation.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Iconica Standard Component Documentation Workflow
|
||||
# Workflow Caller version: 1.0.0
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-iconica-component-documentation-workflow:
|
||||
uses: Iconica-Development/.github/.github/workflows/component-documentation.yml@master
|
||||
secrets: inherit
|
||||
permissions: write-all
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -23,7 +23,7 @@ migrate_working_dir/
|
|||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
|
@ -37,3 +37,8 @@ example/windows/
|
|||
example/web/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.metadata
|
||||
|
||||
# FVM Version Cache
|
||||
.fvm/
|
||||
.fvmrc
|
||||
|
|
10
.metadata
10
.metadata
|
@ -1,10 +0,0 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -1,3 +1,23 @@
|
|||
## 1.6.0
|
||||
* Upgraded flutter_input_library to 3.6.0
|
||||
|
||||
## 1.5.0
|
||||
|
||||
- Updated flutter_input_library to 3.2.1
|
||||
- Added the option to give a `BoxFit` to the avatar image
|
||||
|
||||
## 1.4.0
|
||||
|
||||
- Added CONTRIBUTING.md, documentation and updated flutter_input_library to 3.2.1
|
||||
|
||||
## 1.3.0
|
||||
|
||||
- Field has been added so the user can provide it's current password for reauthentication.
|
||||
|
||||
## 1.2.1
|
||||
|
||||
- Added Iconica CI and Iconica Linter
|
||||
|
||||
## 1.2.0
|
||||
|
||||
- Added the posibilty to enable the user to change it's password.
|
||||
|
|
198
CONTRIBUTING.md
Normal file
198
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,198 @@
|
|||
# Contributing
|
||||
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
All types of contributions are encouraged and valued.
|
||||
See the [Table of Contents](#table-of-contents) for different ways to help and details about how we handle them.
|
||||
Please make sure to read the relevant section before making your contribution.
|
||||
It will make it a lot easier for us maintainers and smooth out the experience for all involved.
|
||||
Iconica looks forward to your contributions. 🎉
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Code of conduct](#code-of-conduct)
|
||||
- [I Have a Question](#i-have-a-question)
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Contributing code](#contributing-code)
|
||||
|
||||
## Code of conduct
|
||||
|
||||
### Legal notice
|
||||
|
||||
When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
|
||||
All accepted pull requests and other additions to this project will be considered intellectual property of Iconica.
|
||||
|
||||
All repositories should be kept clean of jokes, easter eggs and other unnecessary additions.
|
||||
|
||||
## I have a question
|
||||
|
||||
If you want to ask a question, we assume that you have read the available documentation found within the code.
|
||||
Before you ask a question, it is best to search for existing issues that might help you.
|
||||
In case you have found a suitable issue but still need clarification, you can ask your question
|
||||
It is also advisable to search the internet for answers first.
|
||||
|
||||
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||
|
||||
- Open an issue.
|
||||
- Provide as much context as you can about what you're running into.
|
||||
|
||||
We will then take care of the issue as soon as possible.
|
||||
|
||||
## I want to contribute
|
||||
|
||||
### Reporting bugs
|
||||
|
||||
<!-- omit in toc -->
|
||||
|
||||
**Before submitting a bug report**
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information.
|
||||
Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report.
|
||||
Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error.
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if users outside of Iconica have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Time and date of occurance
|
||||
- Describe the expected result and actual result
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions? Describe all steps that lead to the bug.
|
||||
|
||||
Once it's filed:
|
||||
|
||||
- The project team will label the issue accordingly.
|
||||
- A team member will try to reproduce the issue with your provided steps.
|
||||
If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for additional information.
|
||||
- If the team is able to reproduce the issue, it will be moved into the backlog, as well as marked with a priority, and the issue will be left to be [implemented by someone](#contributing-code).
|
||||
|
||||
### Contributing code
|
||||
|
||||
When you start working on your contribution, make sure you are aware of the relevant documentation and the functionality of the component you are working on.
|
||||
|
||||
When writing code, follow the style guidelines set by Dart: [Effective Dart](https://Dart.dev/guides/language/effective-Dart). This contains most information you will need to write clean Dart code that is well documented.
|
||||
|
||||
**Documentation**
|
||||
|
||||
As Effective Dart indicates, documenting your public methods with Dart doc comments is recommended.
|
||||
Aside from Effective Dart, we require specific information in the documentation of a method:
|
||||
|
||||
At the very least, your documentation should first name what the code does, then followed below by requirements for calling the method, the result of the method.
|
||||
Any references to internal variables or other methods should be done through [var] to indicate a reference.
|
||||
|
||||
If the method or class is complex enough (determined by the reviewers) an example is required.
|
||||
If unsure, add an example in the docs using code blocks.
|
||||
|
||||
For classes and methods, document the individual parameters with their implications.
|
||||
|
||||
> Tip: Remember that the shortest documentation can be written by having good descriptive names in the first place.
|
||||
|
||||
An example:
|
||||
|
||||
````Dart
|
||||
library iconica_utilities.bidirectional_sorter;
|
||||
|
||||
part 'sorter.Dart';
|
||||
part 'enum.Dart';
|
||||
|
||||
/// Generic sort method, allow sorting of list with primitives or complex types.
|
||||
/// Uses [SortDirection] to determine the direction, either Ascending or Descending,
|
||||
/// Gets called on [List] toSort of type [T] which cannot be shorter than 2.
|
||||
/// Optionally for complex types a [Comparable] [Function] can be given to compare complex types.
|
||||
/// ```
|
||||
/// List<TestObject> objects = [];
|
||||
/// for (int i = 0; i < 10; i++) {
|
||||
/// objects.add(TestObject(name: "name", id: i));
|
||||
/// }
|
||||
///
|
||||
/// sort<TestObject>(
|
||||
/// SortDirection.descending, objects, (object) => object.id);
|
||||
///
|
||||
/// ```
|
||||
/// In the above example a list of TestObjects is created, and then sorted in descending order.
|
||||
/// If the implementation of TestObject is as following:
|
||||
/// ```
|
||||
/// class TestObject {
|
||||
/// final String name;
|
||||
/// final int id;
|
||||
///
|
||||
/// TestObject({required this.name, required this.id});
|
||||
/// }
|
||||
/// ```
|
||||
/// And the list is logged to the console, the following will appear:
|
||||
/// ```
|
||||
/// [name9, name8, name7, name6, name5, name4, name3, name2, name1, name0]
|
||||
/// ```
|
||||
|
||||
void sort<T>(
|
||||
/// Determines the sorting direction, can be either Ascending or Descending
|
||||
SortDirection sortDirection,
|
||||
|
||||
/// Incoming list, which gets sorted
|
||||
List<T> toSort, [
|
||||
|
||||
/// Optional comparable, which is only necessary for complex types
|
||||
SortFieldGetter<T>? sortValueCallback,
|
||||
]) {
|
||||
if (toSort.length < 2) return;
|
||||
assert(
|
||||
toSort.whereType<Comparable>().isNotEmpty || sortValueCallback != null);
|
||||
BidirectionalSorter<T>(
|
||||
sortInstructions: <SortInstruction<T>>[
|
||||
SortInstruction(
|
||||
sortValueCallback ?? (t) => t as Comparable, sortDirection),
|
||||
],
|
||||
).sort(toSort);
|
||||
}
|
||||
|
||||
/// same functionality as [sort] but with the added functionality
|
||||
/// of sorting multiple values
|
||||
void sortMulti<T>(
|
||||
/// Incoming list, which gets sorted
|
||||
List<T> toSort,
|
||||
|
||||
/// list of comparables to sort multiple values at once,
|
||||
/// priority based on index
|
||||
List<SortInstruction<T>> sortValueCallbacks,
|
||||
) {
|
||||
if (toSort.length < 2) return;
|
||||
assert(sortValueCallbacks.isNotEmpty);
|
||||
BidirectionalSorter<T>(
|
||||
sortInstructions: sortValueCallbacks,
|
||||
).sort(toSort);
|
||||
}
|
||||
|
||||
````
|
||||
|
||||
**Tests**
|
||||
|
||||
For each public method that was created, excluding widgets, which contains any form of logic (e.g. Calculations, predicates or major side-effects) tests are required.
|
||||
|
||||
A set of tests is written for each method, covering at least each path within the method. For example:
|
||||
|
||||
```Dart
|
||||
void foo() {
|
||||
try {
|
||||
var bar = doSomething();
|
||||
if (bar) {
|
||||
doSomethingElse();
|
||||
} else {
|
||||
doSomethingCool();
|
||||
}
|
||||
} catch (_) {
|
||||
displayError();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The method above should result in 3 tests:
|
||||
|
||||
1. A test for the path leading to displayError by the cause of an exception
|
||||
2. A test for if bar is true, resulting in doSomethingElse()
|
||||
3. A test for if bar is false, resulting in the doSomethingCool() method being called.
|
||||
|
||||
This means that we require 100% coverage of each method you test.
|
|
@ -31,6 +31,7 @@ Underneath are all paramters, of the 'ProfilePage' widget, listed with an explan
|
|||
| itemBuilderOptions | The options used by the standard itemBuilder to alter the function and style of the textfields |
|
||||
| prioritizedItems | The items that are displayed at the top of the page. Before all the other items in the list and the default items |
|
||||
|
||||
By default input fields are saved after pressing 'enter' inside of the input field.
|
||||
|
||||
## Issues
|
||||
|
||||
|
@ -38,7 +39,7 @@ Please file any issues, bugs or feature request as an issue on our [GitHub](http
|
|||
|
||||
## Want to contribute
|
||||
|
||||
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_profile/pulls).
|
||||
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_profile/pulls).
|
||||
|
||||
## Author
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
- platform: android
|
||||
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
- platform: ios
|
||||
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
- platform: linux
|
||||
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
- platform: macos
|
||||
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
- platform: web
|
||||
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
- platform: windows
|
||||
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
|
@ -52,62 +52,67 @@ class _ProfileExampleState extends State<ProfileExample> {
|
|||
var width = MediaQuery.of(context).size.width;
|
||||
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: ProfilePage(
|
||||
changePasswordConfig:
|
||||
const ChangePasswordConfig(enablePasswordChange: true),
|
||||
wrapViewOptions: WrapViewOptions(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 16,
|
||||
),
|
||||
bottomActionText: 'Log out',
|
||||
itemBuilderOptions: ItemBuilderOptions(
|
||||
//no label for email
|
||||
validators: {
|
||||
'first_name': (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Field empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
'last_name': (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;
|
||||
},
|
||||
body: ProfilePage(
|
||||
changePasswordConfig:
|
||||
const ChangePasswordConfig(enablePasswordChange: true),
|
||||
wrapViewOptions: WrapViewOptions(
|
||||
spacing: 8,
|
||||
direction: Axis.vertical,
|
||||
),
|
||||
bottomActionText: 'Log out',
|
||||
itemBuilderOptions: ItemBuilderOptions(
|
||||
//no label for email
|
||||
validators: {
|
||||
'first_name': (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Field empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
inputDecorationField: {
|
||||
'password_1': const InputDecoration(
|
||||
'last_name': (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;
|
||||
},
|
||||
},
|
||||
|
||||
inputDecorationField: {
|
||||
'current_password': const InputDecoration(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 60,
|
||||
maxWidth: 200,
|
||||
maxWidth: 250,
|
||||
),
|
||||
),
|
||||
'password_2': const InputDecoration(
|
||||
hintText: 'Current password'),
|
||||
'password_1': const InputDecoration(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 60,
|
||||
maxWidth: 200,
|
||||
maxWidth: 250,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
user: _user,
|
||||
service: ExampleProfileService(),
|
||||
style: ProfileStyle(
|
||||
avatarTextStyle: const TextStyle(fontSize: 20),
|
||||
pagePadding: EdgeInsets.only(
|
||||
top: 50,
|
||||
bottom: 50,
|
||||
left: width * 0.1,
|
||||
right: width * 0.1,
|
||||
),
|
||||
hintText: 'New password'),
|
||||
'password_2': const InputDecoration(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 60,
|
||||
maxWidth: 250,
|
||||
),
|
||||
hintText: 'Repeat new password'),
|
||||
},
|
||||
),
|
||||
user: _user,
|
||||
service: ExampleProfileService(),
|
||||
style: ProfileStyle(
|
||||
avatarTextStyle: const TextStyle(fontSize: 20),
|
||||
pagePadding: EdgeInsets.only(
|
||||
top: 50,
|
||||
bottom: 50,
|
||||
left: width * 0.1,
|
||||
right: width * 0.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -17,7 +17,7 @@ class ExampleProfileData extends ProfileData {
|
|||
String? remarks;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> mapWidget(
|
||||
Map<String, Widget?> mapWidget(
|
||||
VoidCallback update,
|
||||
BuildContext context,
|
||||
) {
|
||||
|
@ -38,7 +38,7 @@ class ExampleProfileData extends ProfileData {
|
|||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
Map<String, String?> toMap() {
|
||||
return {'email': email, 'about': about, 'remarks': remarks};
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,10 @@ class ExampleProfileService extends ProfileService {
|
|||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> changePassword(String password) {
|
||||
debugPrint(password);
|
||||
FutureOr<bool> changePassword(
|
||||
BuildContext context, String currentPassword, String newPassword) {
|
||||
debugPrint('$currentPassword -> $newPassword');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,437 +0,0 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
cached_network_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image
|
||||
sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
cached_network_image_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
name: flutter_lints
|
||||
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_profile:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.1.6"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.5"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
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:
|
||||
name: lints
|
||||
sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: octo_image
|
||||
sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "050e8e85e4b7fecdf2bb3682c1c64c4887a183720c802d323de8a5fd76d372dd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.22"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
sha256: "03d639406f5343478352433f00d3c4394d52dac8df3d847869c5e2333e0bbce8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.7"
|
||||
path_provider_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_macos
|
||||
sha256: "2a97e7fbb7ae9dcd0dfc1220a78e9ec3e71da691912e617e8715ff2a13086ae8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: d21c022832f139b89922738e200c07387a49c549bf36c35654418e19ff76d161
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0+3"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "0c21a187d645aa65da5be6997c0c713eed61e049158870ae2de157e6897067ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0+2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "7b530acd9cb7c71b0019a1e7fa22c4105e675557a4400b6a401c71c5e0ade1ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0+3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: d13ac5deea7327f027b3b97ee19ee210f68256ecf3f1a304bcfb992ee947637c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "11541eedefbcaec9de35aa82650b695297ce668662bbd6e3911a7fabdbde589f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0+2"
|
||||
sdks:
|
||||
dart: ">=3.2.0-194.0.dev <4.0.0"
|
||||
flutter: ">=3.10.0"
|
|
@ -1,15 +1,15 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
///
|
||||
library flutter_profile;
|
||||
|
||||
export 'src/models/change_password_config.dart';
|
||||
export 'src/models/user.dart';
|
||||
export 'src/services/profile_service.dart';
|
||||
export 'src/widgets/avatar/avatar.dart';
|
||||
export 'src/widgets/avatar/avatar_wrapper.dart';
|
||||
export 'src/widgets/item_builder/item_builder.dart';
|
||||
export 'src/widgets/item_builder/item_builder_options.dart';
|
||||
export 'src/widgets/profile/profile_page.dart';
|
||||
export 'src/widgets/profile/profile_style.dart';
|
||||
export 'src/widgets/avatar/avatar_wrapper.dart';
|
||||
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';
|
||||
|
|
|
@ -39,6 +39,7 @@ class ChangePasswordConfig {
|
|||
/// 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.
|
||||
/// Error text to be shown when the second password isn't equal
|
||||
/// to the first password.
|
||||
final String notEqualErrorText;
|
||||
}
|
||||
|
|
|
@ -10,12 +10,6 @@ import 'package:flutter/material.dart';
|
|||
///
|
||||
/// For additional data profileData can be used.
|
||||
class User {
|
||||
String? firstName;
|
||||
String? lastName;
|
||||
Uint8List? image;
|
||||
String? imageUrl;
|
||||
ProfileData? profileData;
|
||||
|
||||
User({
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
|
@ -24,10 +18,6 @@ class User {
|
|||
this.profileData,
|
||||
});
|
||||
|
||||
String get displayName => '${firstName ?? ''} ${lastName ?? ''}';
|
||||
String get initials =>
|
||||
'${(firstName?.isNotEmpty ?? false) ? firstName![0] : ''}${(lastName?.isNotEmpty ?? false) ? lastName![0] : ''}';
|
||||
|
||||
factory User.fromMap(Map<String, dynamic> data) => User(
|
||||
firstName: data['first_name'],
|
||||
lastName: data['last_name'],
|
||||
|
@ -36,6 +26,35 @@ class User {
|
|||
profileData: data['profile_data'],
|
||||
);
|
||||
|
||||
/// The first name of the user.
|
||||
String? firstName;
|
||||
|
||||
/// The last name of the user.
|
||||
String? lastName;
|
||||
|
||||
/// The image of the user, stored as Uint8List.
|
||||
Uint8List? image;
|
||||
|
||||
/// The URL of the user's image.
|
||||
String? imageUrl;
|
||||
|
||||
/// Additional profile data for the user.
|
||||
ProfileData? profileData;
|
||||
|
||||
/// The display name of the user, which is a combination of
|
||||
/// the first name and the last name.
|
||||
/// If the first name or the last name is null,
|
||||
/// an empty string is used instead.
|
||||
String get displayName => '${firstName ?? ''} ${lastName ?? ''}';
|
||||
|
||||
/// The initials of the user, which are the first characters
|
||||
/// of the first name and the last name.
|
||||
/// If the first name or the last name is null or empty,
|
||||
/// an empty string is used instead.
|
||||
String get initials =>
|
||||
'${(firstName?.isNotEmpty ?? false) ? firstName![0] : ''}'
|
||||
'${(lastName?.isNotEmpty ?? false) ? lastName![0] : ''}';
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
'first_name': firstName,
|
||||
'last_name': lastName,
|
||||
|
@ -47,17 +66,19 @@ class User {
|
|||
|
||||
/// 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 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.
|
||||
/// The Builditems method is used to make the list of
|
||||
/// field to house the user data.
|
||||
abstract class ProfileData {
|
||||
const ProfileData();
|
||||
|
||||
ProfileData fromMap(Map<String, dynamic> data);
|
||||
|
||||
Map<String, dynamic> toMap();
|
||||
Map<String, String?> toMap();
|
||||
|
||||
Map<String, dynamic> mapWidget(VoidCallback update, BuildContext context);
|
||||
Map<String, Widget?> mapWidget(VoidCallback update, BuildContext context);
|
||||
|
||||
ProfileData create();
|
||||
}
|
||||
|
|
|
@ -6,13 +6,18 @@ 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.
|
||||
/// 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.
|
||||
/// 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.
|
||||
///
|
||||
/// changePassword is called when the user requests to change his password.
|
||||
/// Return true to clear the inputfields.
|
||||
abstract class ProfileService {
|
||||
const ProfileService();
|
||||
|
||||
|
@ -22,8 +27,13 @@ abstract class ProfileService {
|
|||
|
||||
FutureOr<void> uploadImage(
|
||||
BuildContext context, {
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
required Function(bool isUploading) onUploadStateChanged,
|
||||
});
|
||||
|
||||
FutureOr<void> changePassword(String password);
|
||||
FutureOr<bool> changePassword(
|
||||
BuildContext context,
|
||||
String currentPassword,
|
||||
String newPassword,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,16 +8,24 @@ import 'package:flutter_profile/src/models/user.dart';
|
|||
|
||||
class Avatar extends StatelessWidget {
|
||||
const Avatar({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.user,
|
||||
this.size = 100,
|
||||
this.avatarBackgroundColor,
|
||||
}) : super(key: key);
|
||||
this.boxfit = BoxFit.contain,
|
||||
});
|
||||
|
||||
/// The user object containing user information.
|
||||
final User? user;
|
||||
|
||||
/// Size of the avatar.
|
||||
final double size;
|
||||
|
||||
/// Background color of the avatar.
|
||||
final Color? avatarBackgroundColor;
|
||||
|
||||
final BoxFit boxfit;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var imageProvider = _getImageProvider(user);
|
||||
|
@ -37,7 +45,7 @@ class Avatar extends StatelessWidget {
|
|||
image: hasImage
|
||||
? DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.contain,
|
||||
fit: boxfit,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
@ -54,6 +62,7 @@ class Avatar extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
/// Returns the image provider based on user's image or image URL.
|
||||
ImageProvider? _getImageProvider(User? user) {
|
||||
if (user?.image != null) {
|
||||
return MemoryImage(user!.image!);
|
||||
|
@ -63,6 +72,8 @@ class Avatar extends StatelessWidget {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Generates a color based on the initials of the user's
|
||||
/// first name and last name.
|
||||
Color _generateColorWithIntials(String? firstName, String? lastName) {
|
||||
var idFirstName = 0;
|
||||
var idLastName = 0;
|
||||
|
|
|
@ -8,24 +8,37 @@ import 'package:flutter_profile/src/widgets/avatar/avatar.dart';
|
|||
|
||||
class AvatarWrapper extends StatelessWidget {
|
||||
const AvatarWrapper({
|
||||
Key? key,
|
||||
required this.user,
|
||||
super.key,
|
||||
this.showName = false,
|
||||
this.padding = const EdgeInsets.only(top: 16),
|
||||
this.size = 100,
|
||||
this.textStyle,
|
||||
this.customAvatar,
|
||||
this.avatarBackgroundColor,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
/// The user object containing user information.
|
||||
final User user;
|
||||
|
||||
/// Custom widget to be used as an avatar.
|
||||
final Widget? customAvatar;
|
||||
|
||||
/// Background color of the avatar.
|
||||
final Color? avatarBackgroundColor;
|
||||
|
||||
/// Whether to show the user's name beneath the avatar.
|
||||
final bool showName;
|
||||
|
||||
/// Padding around the avatar and the name.
|
||||
final EdgeInsets padding;
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// Size of the avatar.
|
||||
final double size;
|
||||
|
||||
/// Style for the user's name.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var avatar = customAvatar ??
|
||||
|
@ -43,8 +56,8 @@ class AvatarWrapper extends StatelessWidget {
|
|||
padding: padding,
|
||||
child: Flexible(
|
||||
child: Text(
|
||||
style: textStyle,
|
||||
user.displayName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -6,7 +6,8 @@ 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.
|
||||
/// ItemBuilder is used to set the standard textfield for each undefined
|
||||
/// users data item.
|
||||
///
|
||||
/// Options sets options for the textfield.
|
||||
class ItemBuilder {
|
||||
|
@ -16,11 +17,16 @@ class ItemBuilder {
|
|||
|
||||
final ItemBuilderOptions options;
|
||||
|
||||
Widget build(String key, dynamic value, Widget? widget,
|
||||
Function(String) updateItem, Function(String?) saveItem) {
|
||||
Widget build(
|
||||
String key,
|
||||
String? value,
|
||||
Widget? widget,
|
||||
void Function(String) updateItem,
|
||||
void Function(String?) saveItem,
|
||||
) {
|
||||
if (widget == null) {
|
||||
var controller = TextEditingController(
|
||||
text: '${value ?? ''}',
|
||||
text: value ?? '',
|
||||
);
|
||||
|
||||
var inputDecoration =
|
||||
|
@ -40,9 +46,7 @@ class ItemBuilder {
|
|||
onSaved: (newValue) {
|
||||
saveItem(newValue);
|
||||
},
|
||||
validator: (value) {
|
||||
return options.validators?[key]?.call(value);
|
||||
},
|
||||
validator: (value) => options.validators?[key]?.call(value),
|
||||
);
|
||||
}
|
||||
return widget;
|
||||
|
@ -50,13 +54,15 @@ class ItemBuilder {
|
|||
|
||||
Widget buildPassword(
|
||||
String key,
|
||||
Function(String?) onChanged,
|
||||
TextEditingController controller,
|
||||
void Function(String?) onChanged,
|
||||
String? Function(String?) validator,
|
||||
) {
|
||||
var inputDecoration =
|
||||
options.inputDecorationField?[key] ?? options.inputDecoration;
|
||||
|
||||
return FlutterFormInputPassword(
|
||||
controller: controller,
|
||||
style: options.inputTextStyle,
|
||||
decoration: inputDecoration,
|
||||
onChanged: onChanged,
|
||||
|
|
|
@ -4,17 +4,22 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// ItemBuilderOptions is a class to store all settings for a field in the profile page.
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// 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 {
|
||||
ItemBuilderOptions({
|
||||
this.inputDecoration = const InputDecoration(
|
||||
constraints: BoxConstraints(maxWidth: 200, maxHeight: 40)),
|
||||
constraints: BoxConstraints(maxWidth: 200, maxHeight: 40),
|
||||
),
|
||||
this.inputDecorationField,
|
||||
this.readOnly = false,
|
||||
this.validators,
|
||||
|
|
|
@ -16,46 +16,56 @@ class ItemList {
|
|||
this.itemBuilder,
|
||||
this.itemBuilderOptions,
|
||||
}) {
|
||||
for (var item in items.entries) {
|
||||
widgets.addAll({
|
||||
item.key: itemBuilder == null
|
||||
? builder.build(
|
||||
item.key,
|
||||
item.value,
|
||||
typeMap[item.key],
|
||||
(value) {
|
||||
saveProfile();
|
||||
},
|
||||
(value) {
|
||||
updateProfile(item.key, value);
|
||||
},
|
||||
)
|
||||
: itemBuilder!.build(
|
||||
item.key,
|
||||
item.value,
|
||||
typeMap[item.key],
|
||||
(value) {
|
||||
saveProfile();
|
||||
},
|
||||
(value) {
|
||||
updateProfile(item.key, value);
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
var itemBuilder = this.itemBuilder ?? builder;
|
||||
|
||||
widgets = {
|
||||
for (var item in items.entries) ...{
|
||||
item.key: itemBuilder.build(
|
||||
item.key,
|
||||
item.value,
|
||||
typeMap[item.key],
|
||||
(value) {
|
||||
saveProfile();
|
||||
},
|
||||
(value) {
|
||||
updateProfile(item.key, value);
|
||||
},
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Gets the map of item keys and their corresponding widgets.
|
||||
Map<String, Widget> getItemList() => widgets;
|
||||
|
||||
final Map<String, dynamic> items;
|
||||
final Map<String, dynamic> typeMap;
|
||||
final Function(String, String?) updateProfile;
|
||||
final Function() saveProfile;
|
||||
/// Map containing item keys and their values.
|
||||
final Map<String, String?> items;
|
||||
|
||||
/// Map containing item keys and their types.
|
||||
final Map<String, Widget?> typeMap;
|
||||
|
||||
/// Function to update the profile with a specific item's value.
|
||||
final void Function(String, String?) updateProfile;
|
||||
|
||||
/// Function to save the profile after an item value is updated.
|
||||
final void Function() saveProfile;
|
||||
|
||||
/// Builder for custom item widgets.
|
||||
final ItemBuilder? itemBuilder;
|
||||
|
||||
/// Options for the item builder.
|
||||
final ItemBuilderOptions? itemBuilderOptions;
|
||||
|
||||
/// Global key for the form associated with the item list.
|
||||
final GlobalKey<FormState> formKey;
|
||||
|
||||
Map<String, Widget> widgets = {};
|
||||
/// Map containing item keys and their corresponding widgets.
|
||||
late final Map<String, Widget> widgets;
|
||||
|
||||
/// `builder` is an instance of `ItemBuilder` which is used
|
||||
/// to build the items in the list.
|
||||
/// If `itemBuilderOptions` is not provided, a default
|
||||
/// `ItemBuilderOptions` instance is used.
|
||||
late ItemBuilder builder = ItemBuilder(
|
||||
options: itemBuilderOptions ?? ItemBuilderOptions(),
|
||||
);
|
||||
|
|
|
@ -13,12 +13,25 @@ class ChangePassword extends StatefulWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
/// Configuration for changing password.
|
||||
final ChangePasswordConfig config;
|
||||
|
||||
/// Options for wrapping the items.
|
||||
final WrapViewOptions? wrapViewOptions;
|
||||
final ItemBuilder? itemBuilder;
|
||||
final ItemBuilderOptions? itemBuilderOptions;
|
||||
|
||||
/// Builder for wrapping items.
|
||||
final Widget Function(BuildContext context, Widget child)? wrapItemsBuilder;
|
||||
|
||||
/// Builder for creating items.
|
||||
final ItemBuilder? itemBuilder;
|
||||
|
||||
/// Options for item builder.
|
||||
final ItemBuilderOptions? itemBuilderOptions;
|
||||
|
||||
/// Styling options for the widget.
|
||||
final ProfileStyle style;
|
||||
|
||||
/// Profile service for managing user profile.
|
||||
final ProfileService service;
|
||||
|
||||
@override
|
||||
|
@ -32,6 +45,11 @@ class _ChangePasswordState extends State<ChangePassword> {
|
|||
|
||||
late final Widget? changePasswordChild;
|
||||
|
||||
late var currentPasswordController = TextEditingController();
|
||||
late var password1Controller = TextEditingController();
|
||||
late var password2Controller = TextEditingController();
|
||||
|
||||
String? currentPassword;
|
||||
String? password1;
|
||||
String? password2;
|
||||
|
||||
|
@ -51,8 +69,21 @@ class _ChangePasswordState extends State<ChangePassword> {
|
|||
runSpacing: widget.wrapViewOptions?.runSpacing ?? 0,
|
||||
clipBehavior: widget.wrapViewOptions?.clipBehavior ?? Clip.none,
|
||||
children: [
|
||||
builder.buildPassword(
|
||||
'current_password',
|
||||
currentPasswordController,
|
||||
(value) => currentPassword = value,
|
||||
(value) {
|
||||
if (currentPassword?.isEmpty ?? true) {
|
||||
return config.fieldRequiredErrorText;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
builder.buildPassword(
|
||||
'password_1',
|
||||
password1Controller,
|
||||
(value) => password1 = value,
|
||||
(value) {
|
||||
if (password1?.isEmpty ?? true) {
|
||||
|
@ -64,6 +95,7 @@ class _ChangePasswordState extends State<ChangePassword> {
|
|||
),
|
||||
builder.buildPassword(
|
||||
'password_2',
|
||||
password2Controller,
|
||||
(value) => password2 = value,
|
||||
(value) {
|
||||
if (password2?.isEmpty ?? true) {
|
||||
|
@ -89,15 +121,27 @@ class _ChangePasswordState extends State<ChangePassword> {
|
|||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
|
||||
void onTapSave() {
|
||||
if ((_formKey.currentState?.validate() ?? false) && password2 != null) {
|
||||
widget.service.changePassword(password2!);
|
||||
Future<void> onTapSave() async {
|
||||
if ((_formKey.currentState?.validate() ?? false) &&
|
||||
currentPassword != null &&
|
||||
password2 != null) {
|
||||
if (await widget.service
|
||||
.changePassword(context, currentPassword!, password2!)) {
|
||||
currentPasswordController.clear();
|
||||
password1Controller.clear();
|
||||
password2Controller.clear();
|
||||
|
||||
currentPassword = null;
|
||||
password1 = null;
|
||||
password2 = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: widget.style.betweenDefaultItemPadding * 2.5,
|
||||
|
@ -121,13 +165,12 @@ class _ChangePasswordState extends State<ChangePassword> {
|
|||
),
|
||||
config.saveButtonBuilder?.call(
|
||||
context,
|
||||
() => onTapSave(),
|
||||
onTapSave,
|
||||
) ??
|
||||
FilledButton(
|
||||
onPressed: () => onTapSave(),
|
||||
onPressed: onTapSave,
|
||||
child: const Text('Save password'),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -11,28 +11,37 @@ import 'package:flutter_profile/src/widgets/item_builder/item_builder_options.da
|
|||
import 'package:flutter_profile/src/widgets/profile/profile_style.dart';
|
||||
import 'package:flutter_profile/src/widgets/profile/profile_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:
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// 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 {
|
||||
const ProfilePage({
|
||||
Key? key,
|
||||
required this.user,
|
||||
required this.service,
|
||||
super.key,
|
||||
this.style = const ProfileStyle(),
|
||||
this.customAvatar,
|
||||
this.showAvatar = true,
|
||||
|
@ -49,7 +58,7 @@ class ProfilePage extends StatefulWidget {
|
|||
this.formKey,
|
||||
this.changePasswordConfig =
|
||||
const ChangePasswordConfig(enablePasswordChange: false),
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
/// User containing all the user data.
|
||||
final User user;
|
||||
|
@ -69,10 +78,12 @@ class ProfilePage extends StatefulWidget {
|
|||
///The background color of the avatar when no image is available.
|
||||
final Color? avatarBackgroundColor;
|
||||
|
||||
/// Whether you want to show the input fields, sometimes you just want to edit the avatar.
|
||||
/// Whether you want to show the input fields, sometimes you just want
|
||||
/// to edit the avatar.
|
||||
final bool showItems;
|
||||
|
||||
/// Sets the text for the [InkWell] at the bottom of the profile page. The [InkWell] is disabled when null.
|
||||
/// 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.
|
||||
|
@ -84,7 +95,8 @@ class ProfilePage extends StatefulWidget {
|
|||
/// Customize the parent widget for all fields
|
||||
final Widget Function(BuildContext context, Widget child)? wrapItemsBuilder;
|
||||
|
||||
/// Map keys of items that should be shown first before the default items and the rest of the items.
|
||||
/// Map keys of items that should be shown first before the default
|
||||
/// items and the rest of the items.
|
||||
final List<String> prioritizedItems;
|
||||
|
||||
/// Shows textfields for firstname and lastname if is set to true
|
||||
|
@ -93,7 +105,8 @@ class ProfilePage extends StatefulWidget {
|
|||
/// Edit the direction and spacing between every item
|
||||
final WrapViewOptions? wrapViewOptions;
|
||||
|
||||
/// The map of extra widgets that might want to be added like empty SizedBoxes for styling.
|
||||
/// The map of extra widgets that might want to be added like empty
|
||||
/// SizedBoxes for styling.
|
||||
final Map<String, Widget>? extraWidgets;
|
||||
|
||||
/// Use the form key to save on any custom callback
|
||||
|
@ -108,42 +121,51 @@ class ProfilePage extends StatefulWidget {
|
|||
|
||||
class _ProfilePageState extends State<ProfilePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProfileWrapper(
|
||||
service: widget.service,
|
||||
user: widget.user,
|
||||
rebuild: () {
|
||||
setState(() {});
|
||||
},
|
||||
style: widget.style,
|
||||
customAvatar: widget.customAvatar,
|
||||
showAvatar: widget.showAvatar,
|
||||
showItems: widget.showItems,
|
||||
bottomActionText: widget.bottomActionText,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
itemBuilderOptions: widget.itemBuilderOptions,
|
||||
prioritizedItems: widget.prioritizedItems,
|
||||
showDefaultItems: widget.showDefaultItems,
|
||||
wrapItemsBuilder: widget.wrapItemsBuilder,
|
||||
wrapViewOptions: widget.wrapViewOptions,
|
||||
extraWidgets: widget.extraWidgets,
|
||||
formKey: widget.formKey,
|
||||
avatarBackgroundColor: widget.avatarBackgroundColor,
|
||||
changePasswordConfig: widget.changePasswordConfig,
|
||||
);
|
||||
}
|
||||
Widget build(BuildContext context) => ProfileWrapper(
|
||||
service: widget.service,
|
||||
user: widget.user,
|
||||
rebuild: () {
|
||||
setState(() {});
|
||||
},
|
||||
style: widget.style,
|
||||
customAvatar: widget.customAvatar,
|
||||
showAvatar: widget.showAvatar,
|
||||
showItems: widget.showItems,
|
||||
bottomActionText: widget.bottomActionText,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
itemBuilderOptions: widget.itemBuilderOptions,
|
||||
prioritizedItems: widget.prioritizedItems,
|
||||
showDefaultItems: widget.showDefaultItems,
|
||||
wrapItemsBuilder: widget.wrapItemsBuilder,
|
||||
wrapViewOptions: widget.wrapViewOptions,
|
||||
extraWidgets: widget.extraWidgets,
|
||||
formKey: widget.formKey,
|
||||
avatarBackgroundColor: widget.avatarBackgroundColor,
|
||||
changePasswordConfig: widget.changePasswordConfig,
|
||||
);
|
||||
}
|
||||
|
||||
class WrapViewOptions {
|
||||
WrapViewOptions(
|
||||
{this.direction,
|
||||
this.spacing,
|
||||
this.wrapAlignment,
|
||||
this.runSpacing,
|
||||
this.clipBehavior});
|
||||
WrapViewOptions({
|
||||
this.direction,
|
||||
this.spacing,
|
||||
this.wrapAlignment,
|
||||
this.runSpacing,
|
||||
this.clipBehavior,
|
||||
});
|
||||
|
||||
/// The direction to use as the main axis.
|
||||
Axis? direction;
|
||||
|
||||
/// The distance between adjacent children in the cross axis.
|
||||
double? spacing;
|
||||
double? runSpacing;
|
||||
Clip? clipBehavior;
|
||||
|
||||
/// How the children should be placed along the main axis.
|
||||
WrapAlignment? wrapAlignment;
|
||||
|
||||
/// The distance between adjacent children in the main axis.
|
||||
double? runSpacing;
|
||||
|
||||
/// Determines how visual overflow should be handled.
|
||||
Clip? clipBehavior;
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// ProfielStyle is used to set a couple of style paramaters for the whole profile page.
|
||||
/// 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.
|
||||
/// 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 {
|
||||
|
@ -22,7 +24,8 @@ class ProfileStyle {
|
|||
/// AvatarStyle can be used to set some avatar styling parameters.
|
||||
final TextStyle avatarTextStyle;
|
||||
|
||||
/// PagePadding can be set to determine the padding of the whole page againt the profile page parent.
|
||||
/// PagePadding can be set to determine the padding of the whole page
|
||||
/// againt the profile page parent.
|
||||
final EdgeInsetsGeometry pagePadding;
|
||||
|
||||
/// BetweenDefaultItemPadding sets the
|
||||
|
|
|
@ -38,25 +38,59 @@ class ProfileWrapper extends StatefulWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
/// The user object containing user information.
|
||||
final User user;
|
||||
|
||||
/// The service for managing user profile.
|
||||
final ProfileService service;
|
||||
|
||||
/// The styling options for the profile.
|
||||
final ProfileStyle style;
|
||||
|
||||
/// Custom avatar widget.
|
||||
final Widget? customAvatar;
|
||||
|
||||
/// Flag to show or hide the avatar.
|
||||
final bool showAvatar;
|
||||
|
||||
/// Background color for the avatar.
|
||||
final Color? avatarBackgroundColor;
|
||||
|
||||
/// Text for the bottom action.
|
||||
final String? bottomActionText;
|
||||
|
||||
/// Builder for creating items.
|
||||
final ItemBuilder? itemBuilder;
|
||||
final WrapViewOptions? wrapViewOptions;
|
||||
final Function rebuild;
|
||||
|
||||
/// Options for item builder.
|
||||
final ItemBuilderOptions? itemBuilderOptions;
|
||||
|
||||
/// Options for wrapping the view.
|
||||
final WrapViewOptions? wrapViewOptions;
|
||||
|
||||
/// Callback to rebuild the widget.
|
||||
final Function() rebuild;
|
||||
|
||||
/// Flag to show default items.
|
||||
final bool showDefaultItems;
|
||||
|
||||
/// Flag to show items.
|
||||
final bool showItems;
|
||||
|
||||
/// Builder for wrapping items.
|
||||
final Widget Function(BuildContext context, Widget child)? wrapItemsBuilder;
|
||||
final Map<String, Widget>? extraWidgets;
|
||||
|
||||
/// Key for the form.
|
||||
final GlobalKey<FormState>? formKey;
|
||||
|
||||
/// Additional widgets to be displayed.
|
||||
final Map<String, Widget>? extraWidgets;
|
||||
|
||||
/// Configuration for changing password.
|
||||
final ChangePasswordConfig changePasswordConfig;
|
||||
|
||||
/// Map keys of items that should be shown first before the default items and the rest of the items.
|
||||
/// Map keys of items that should be shown first before the default items and
|
||||
/// the rest of the items.
|
||||
final List<String> prioritizedItems;
|
||||
|
||||
@override
|
||||
|
@ -73,12 +107,12 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_formKey = widget.formKey ?? GlobalKey<FormState>();
|
||||
|
||||
super.initState();
|
||||
if (widget.showDefaultItems) {
|
||||
if (widget.itemBuilder == null) {
|
||||
ItemBuilder builder = ItemBuilder(
|
||||
var builder = ItemBuilder(
|
||||
options: widget.itemBuilderOptions ?? ItemBuilderOptions(),
|
||||
);
|
||||
defaultItems.addAll({
|
||||
|
@ -89,7 +123,7 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
|
|||
(value) {
|
||||
submitAllChangedFields();
|
||||
},
|
||||
(v) {
|
||||
(v) async {
|
||||
if (widget.user.firstName != v) {
|
||||
widget.user.firstName = v;
|
||||
widget.service.editProfile(widget.user, 'first_name', v);
|
||||
|
@ -103,7 +137,7 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
|
|||
(value) {
|
||||
submitAllChangedFields();
|
||||
},
|
||||
(v) {
|
||||
(v) async {
|
||||
if (widget.user.lastName != v) {
|
||||
widget.user.lastName = v;
|
||||
widget.service.editProfile(widget.user, 'last_name', v);
|
||||
|
@ -120,7 +154,7 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
|
|||
(value) {
|
||||
submitAllChangedFields();
|
||||
},
|
||||
(v) {
|
||||
(v) async {
|
||||
if (widget.user.firstName != v) {
|
||||
widget.user.firstName = v;
|
||||
widget.service.editProfile(widget.user, 'first_name', v);
|
||||
|
@ -134,7 +168,7 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
|
|||
(value) {
|
||||
submitAllChangedFields();
|
||||
},
|
||||
(v) {
|
||||
(v) async {
|
||||
if (widget.user.lastName != v) {
|
||||
widget.user.lastName = v;
|
||||
widget.service.editProfile(widget.user, 'last_name', v);
|
||||
|
@ -147,28 +181,28 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
|
|||
widgets.addAll(widget.extraWidgets ?? {});
|
||||
widgets.addAll(defaultItems);
|
||||
if (widget.user.profileData != null) {
|
||||
widgets.addAll(ItemList(
|
||||
Map.fromEntries(widget.user.profileData!.toMap().entries),
|
||||
widget.user.profileData!.mapWidget(
|
||||
() {
|
||||
widget.rebuild();
|
||||
widgets.addAll(
|
||||
ItemList(
|
||||
Map.fromEntries(widget.user.profileData!.toMap().entries),
|
||||
widget.user.profileData!.mapWidget(
|
||||
() {
|
||||
widget.rebuild();
|
||||
},
|
||||
context,
|
||||
),
|
||||
(key, value) async {
|
||||
if (widget.user.profileData?.toMap()[key] == null) {
|
||||
widget.service.editProfile(widget.user, key, value);
|
||||
} else if (widget.user.profileData?.toMap()[key] != value) {
|
||||
widget.service.editProfile(widget.user, key, value);
|
||||
}
|
||||
},
|
||||
context,
|
||||
),
|
||||
(key, value) {
|
||||
if (widget.user.toMap()['profile_data'][key] == null) {
|
||||
widget.service.editProfile(widget.user, key, value);
|
||||
} else if (widget.user.toMap()['profile_data'][key] != value) {
|
||||
widget.service.editProfile(widget.user, key, value);
|
||||
}
|
||||
},
|
||||
() {
|
||||
submitAllChangedFields();
|
||||
},
|
||||
itemBuilder: widget.itemBuilder,
|
||||
itemBuilderOptions: widget.itemBuilderOptions,
|
||||
formKey: _formKey,
|
||||
).getItemList());
|
||||
submitAllChangedFields,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
itemBuilderOptions: widget.itemBuilderOptions,
|
||||
formKey: _formKey,
|
||||
).getItemList(),
|
||||
);
|
||||
}
|
||||
|
||||
var items = Wrap(
|
||||
|
@ -195,96 +229,92 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
|
|||
}
|
||||
|
||||
@override
|
||||
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(
|
||||
children: [
|
||||
if (widget.showAvatar) ...[
|
||||
InkWell(
|
||||
onTap: () => widget.service.uploadImage(
|
||||
context,
|
||||
onUploadStateChanged: (isUploading) => setState(
|
||||
() {
|
||||
_isUploadingImage = isUploading;
|
||||
},
|
||||
Widget build(BuildContext context) => Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: widget.style.pagePadding,
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.showAvatar) ...[
|
||||
InkWell(
|
||||
onTap: () async => widget.service.uploadImage(
|
||||
context,
|
||||
onUploadStateChanged: (isUploading) => setState(
|
||||
() {
|
||||
_isUploadingImage = isUploading;
|
||||
},
|
||||
),
|
||||
),
|
||||
child: AvatarWrapper(
|
||||
avatarBackgroundColor: widget.avatarBackgroundColor,
|
||||
user: widget.user,
|
||||
textStyle: widget.style.avatarTextStyle,
|
||||
customAvatar: _isUploadingImage
|
||||
? Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: widget.customAvatar,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: AvatarWrapper(
|
||||
avatarBackgroundColor: widget.avatarBackgroundColor,
|
||||
user: widget.user,
|
||||
textStyle: widget.style.avatarTextStyle,
|
||||
customAvatar: _isUploadingImage
|
||||
? Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: widget.customAvatar,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: widget.style.betweenDefaultItemPadding,
|
||||
),
|
||||
],
|
||||
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(),
|
||||
SizedBox(
|
||||
height: widget.style.betweenDefaultItemPadding,
|
||||
),
|
||||
],
|
||||
if (widget.showItems) ...[
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
if (widget.changePasswordConfig.enablePasswordChange) ...[
|
||||
ChangePassword(
|
||||
config: widget.changePasswordConfig,
|
||||
service: widget.service,
|
||||
wrapViewOptions: widget.wrapViewOptions,
|
||||
wrapItemsBuilder: widget.wrapItemsBuilder,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
itemBuilderOptions: widget.itemBuilderOptions,
|
||||
style: widget.style,
|
||||
),
|
||||
],
|
||||
],
|
||||
InkWell(
|
||||
onTap: () {
|
||||
widget.service.pageBottomAction();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
widget.bottomActionText!,
|
||||
style: widget.style.bottomActionTextStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (widget.bottomActionText == null &&
|
||||
!widget.changePasswordConfig.enablePasswordChange) ...[
|
||||
const Spacer(),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.bottomActionText != null &&
|
||||
MediaQuery.of(Scaffold.of(context).context).viewInsets.bottom ==
|
||||
0.0) ...[
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: InkWell(
|
||||
onTap: () async => await widget.service.pageBottomAction(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
widget.bottomActionText!,
|
||||
style: widget.style.bottomActionTextStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
/// This calls onSaved on all the fiels which check if they have a new value
|
||||
void submitAllChangedFields() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
_formKey.currentState!.save();
|
||||
}
|
||||
}
|
||||
|
|
17
pubspec.yaml
17
pubspec.yaml
|
@ -1,21 +1,19 @@
|
|||
name: flutter_profile
|
||||
description: Flutter profile package
|
||||
version: 1.2.0
|
||||
version: 1.6.0
|
||||
repository: https://github.com/Iconica-Development/flutter_profile
|
||||
|
||||
publish_to: none
|
||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
|
||||
environment:
|
||||
sdk: ^3.0.0
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
cached_network_image: ^3.3.0
|
||||
|
||||
flutter_input_library:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_input_library
|
||||
ref: 2.7.0
|
||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
version: ^3.6.0
|
||||
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
@ -23,6 +21,9 @@ dependencies:
|
|||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
flutter_iconica_analysis:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||
ref: 6.0.0
|
||||
|
||||
flutter:
|
||||
|
|
|
@ -25,9 +25,9 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
final firstNameFinder = find.text('Firstname');
|
||||
final lastNameFinder = find.text('Lastname');
|
||||
final emailFinder = find.text('test@email.com');
|
||||
var firstNameFinder = find.text('Firstname');
|
||||
var lastNameFinder = find.text('Lastname');
|
||||
var emailFinder = find.text('test@email.com');
|
||||
|
||||
expect(firstNameFinder, findsOneWidget);
|
||||
expect(lastNameFinder, findsOneWidget);
|
||||
|
@ -74,13 +74,13 @@ void main() {
|
|||
await tester.testTextInput.receiveAction(TextInputAction.send);
|
||||
await tester.pump();
|
||||
|
||||
final firstNameFinder = find.text('Firstname');
|
||||
final firstNameEditedFinder = find.text('FirstEditedName');
|
||||
var firstNameFinder = find.text('Firstname');
|
||||
var firstNameEditedFinder = find.text('FirstEditedName');
|
||||
|
||||
final lastNameFinder = find.text('Lastname');
|
||||
var lastNameFinder = find.text('Lastname');
|
||||
|
||||
final emailFinder = find.text('test@email.com');
|
||||
final emailEditedFinder = find.text('edited@emial.com');
|
||||
var emailFinder = find.text('test@email.com');
|
||||
var emailEditedFinder = find.text('edited@emial.com');
|
||||
|
||||
expect(firstNameFinder, findsNothing);
|
||||
expect(firstNameEditedFinder, findsOneWidget);
|
||||
|
|
|
@ -13,7 +13,7 @@ class TestProfileData extends ProfileData {
|
|||
String? email;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> mapWidget(
|
||||
Map<String, Widget?> mapWidget(
|
||||
VoidCallback update,
|
||||
BuildContext context,
|
||||
) =>
|
||||
|
@ -27,7 +27,7 @@ class TestProfileData extends ProfileData {
|
|||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => {
|
||||
Map<String, String?> toMap() => {
|
||||
'email': email,
|
||||
};
|
||||
|
||||
|
|
|
@ -23,9 +23,15 @@ class TestProfileService extends ProfileService {
|
|||
@override
|
||||
FutureOr<void> uploadImage(
|
||||
BuildContext context, {
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
required Function(bool isUploading) onUploadStateChanged,
|
||||
}) {}
|
||||
|
||||
@override
|
||||
FutureOr<void> changePassword(String password) {}
|
||||
FutureOr<bool> changePassword(
|
||||
BuildContext context,
|
||||
String currentPassword,
|
||||
String newPassword,
|
||||
) =>
|
||||
true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue