Merge branch 'feature/add_ci_linter'

This commit is contained in:
mike doornenbal 2024-02-02 16:24:20 +01:00
commit c523678e74
20 changed files with 259 additions and 203 deletions

14
.github/workflows/component-ci.yml vendored Normal file
View 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

View file

@ -1,3 +1,7 @@
## 1.2.1
- Added Iconica CI and Iconica Linter
## 1.2.0
- Added the posibilty to enable the user to change it's password.

View file

@ -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:

View file

@ -141,7 +141,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.1.6"
version: "1.2.0"
flutter_test:
dependency: "direct dev"
description: flutter

View file

@ -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';

View file

@ -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;
}

View file

@ -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'],
@ -35,6 +25,16 @@ class User {
imageUrl: data['image_url'],
profileData: data['profile_data'],
);
String? firstName;
String? lastName;
Uint8List? image;
String? imageUrl;
ProfileData? profileData;
String get displayName => '${firstName ?? ''} ${lastName ?? ''}';
String get initials =>
'${(firstName?.isNotEmpty ?? false) ? firstName![0] : ''}'
'${(lastName?.isNotEmpty ?? false) ? lastName![0] : ''}';
Map<String, dynamic> toMap() => {
'first_name': firstName,
@ -47,9 +47,11 @@ 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();

View file

@ -6,9 +6,11 @@ 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.
///
@ -22,6 +24,7 @@ abstract class ProfileService {
FutureOr<void> uploadImage(
BuildContext context, {
// ignore: avoid_positional_boolean_parameters
required Function(bool isUploading) onUploadStateChanged,
});

View file

@ -8,11 +8,11 @@ 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);
});
final User? user;
final double size;

View file

@ -8,15 +8,15 @@ 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);
});
final User user;
final Widget? customAvatar;

View file

@ -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,8 +17,13 @@ class ItemBuilder {
final ItemBuilderOptions options;
Widget build(String key, dynamic value, Widget? widget,
Function(String) updateItem, Function(String?) saveItem) {
Widget build(
String key,
value,
Widget? widget,
Function(String) updateItem,
Function(String?) saveItem,
) {
if (widget == null) {
var controller = TextEditingController(
text: '${value ?? ''}',
@ -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;

View file

@ -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,

View file

@ -40,7 +40,7 @@ class ItemList {
(value) {
updateProfile(item.key, value);
},
)
),
});
}
}

View file

@ -89,7 +89,7 @@ class _ChangePasswordState extends State<ChangePassword> {
Widget build(BuildContext context) {
var theme = Theme.of(context);
void onTapSave() {
Future<void> onTapSave() async {
if ((_formKey.currentState?.validate() ?? false) && password2 != null) {
widget.service.changePassword(password2!);
}
@ -121,10 +121,10 @@ class _ChangePasswordState extends State<ChangePassword> {
),
config.saveButtonBuilder?.call(
context,
() => onTapSave(),
onTapSave,
) ??
FilledButton(
onPressed: () => onTapSave(),
onPressed: onTapSave,
child: const Text('Save password'),
),
const Spacer(),

View file

@ -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,39 +121,38 @@ 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,
});
Axis? direction;
double? spacing;
double? runSpacing;

View file

@ -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

View file

@ -47,7 +47,7 @@ class ProfileWrapper extends StatefulWidget {
final String? bottomActionText;
final ItemBuilder? itemBuilder;
final WrapViewOptions? wrapViewOptions;
final Function rebuild;
final Function() rebuild;
final ItemBuilderOptions? itemBuilderOptions;
final bool showDefaultItems;
final bool showItems;
@ -56,7 +56,8 @@ class ProfileWrapper extends StatefulWidget {
final GlobalKey<FormState>? formKey;
final ChangePasswordConfig changePasswordConfig;
/// Map keys of items that should be shown first before the default items and the rest of the items.
/// Map keys of items that should be shown first before the default items and
/// the rest of the items.
final List<String> prioritizedItems;
@override
@ -78,7 +79,7 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
super.initState();
if (widget.showDefaultItems) {
if (widget.itemBuilder == null) {
ItemBuilder builder = ItemBuilder(
var builder = ItemBuilder(
options: widget.itemBuilderOptions ?? ItemBuilderOptions(),
);
defaultItems.addAll({
@ -89,7 +90,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 +104,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 +121,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 +135,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 +148,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,92 +196,90 @@ 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) => 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: () async => widget.service.uploadImage(
context,
onUploadStateChanged: (bool 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,
),
),
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.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) ...[
],
if (widget.bottomActionText != null) ...[
SizedBox(
height: widget.style.betweenDefaultItemPadding,
),
if (!widget.changePasswordConfig.enablePasswordChange) ...[
const Spacer(),
],
InkWell(
onTap: () async {
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(),
],
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(),
]
],
),
),
),
),
),
);
}
);
/// This calls onSaved on all the fiels which check if they have a new value
void submitAllChangedFields() {

View file

@ -1,6 +1,6 @@
name: flutter_profile
description: Flutter profile package
version: 1.2.0
version: 1.2.1
repository: https://github.com/Iconica-Development/flutter_profile
publish_to: none
@ -23,6 +23,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:

View file

@ -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);

View file

@ -23,6 +23,7 @@ class TestProfileService extends ProfileService {
@override
FutureOr<void> uploadImage(
BuildContext context, {
// ignore: avoid_positional_boolean_parameters
required Function(bool isUploading) onUploadStateChanged,
}) {}