Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add reset button #18

Merged
merged 3 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## Unreleased
### Added
- Add a reset button [#15](https://github.com/RoadTripMoustache/avatar_maker/issues/15)

### Fixed
- Example configurations
- README
Expand Down
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ Customizer and other utility widgets/functions.

This package provides you three easy-to-use widgets -

| Name | Description | Screenshot |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| AvatarMakerAvatar | Use your avatar anywhere in your Flutter app with a simple customizable widget. Supports material dark theme too. | ![1608830483994](https://user-images.githubusercontent.com/37346450/103071632-009ec100-45ea-11eb-97c4-96c9ec67e204.gif) |
| AvatarMakerCustomizer | A comprehensize UI to customize the user's avatar. Offers previews of each individual component and whose looks can be tweaked with a `AvatarMakerThemeData` | ![1608827561239](https://user-images.githubusercontent.com/37346450/154008536-a687828c-dc9d-4a62-aa11-b800d4fb7763.jpg) |
| AvatarMakerSaveWidget | Renders a save button by default OR can be used as an [InkWell] wrapper for the [child] widget. | ![1608827561239](https://user-images.githubusercontent.com/37346450/154008545-8325af7b-58a2-4419-8544-929ffbdbb9ff.jpg) |
| Name | Description | Screenshot |
|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| [AvatarMakerAvatar](./docs/widgets/avatar.md) | Use your avatar anywhere in your Flutter app with a simple customizable widget. Supports dark theme too. | ![1608830483994](https://user-images.githubusercontent.com/37346450/103071632-009ec100-45ea-11eb-97c4-96c9ec67e204.gif) |
| [AvatarMakerCustomizer](./docs/widgets/customizer.md) | A comprehensize UI to customize the user's avatar. Offers previews of each individual component and whose looks can be tweaked with a `AvatarMakerThemeData` | ![1608827561239](https://user-images.githubusercontent.com/37346450/154008536-a687828c-dc9d-4a62-aa11-b800d4fb7763.jpg) |
| [AvatarMakerResetWidget](./docs/widgets/reset.md) | Renders a reset button by default OR can be used as an [InkWell] wrapper for the [child] widget. It resets the avatar to the last avatar saved state. | ![AvatarMakerResetWidget](https://github.com/RoadTripMoustache/avatar_maker/assets/36586573/ef052b05-1b23-4f77-8a47-1d08fc734958) |
| [AvatarMakerSaveWidget](./docs/widgets/save.md) | Renders a save button by default OR can be used as an [InkWell] wrapper for the [child] widget. | ![1608827561239](https://user-images.githubusercontent.com/37346450/154008545-8325af7b-58a2-4419-8544-929ffbdbb9ff.jpg) |

******

Expand Down Expand Up @@ -110,13 +111,14 @@ worth noting.
your liking.

More details can be found in the widgets documentations or in the how-to :

- Widgets
- [AvatarMakerAvatar](./docs/widgets/avatar.md)
- [AvatarMakerCustomizer](./docs/widgets/customizer.md)
- [AvatarMakerSaveWidget](./docs/widgets/save.md)
- [AvatarMakerAvatar](./docs/widgets/avatar.md)
- [AvatarMakerCustomizer](./docs/widgets/customizer.md)
- [AvatarMakerSaveWidget](./docs/widgets/save.md)
- How-to
- [define a custom theme ?](./docs/how-to/define_custom_theme.md)
- [customize property category ?](./docs/how-to/define_customized_property_category.md)
- [define a custom theme ?](./docs/how-to/define_custom_theme.md)
- [customize property category ?](./docs/how-to/define_customized_property_category.md)

## Attributions

Expand All @@ -135,7 +137,8 @@ designed by [Reesha Shenoy](https://github.com/reeshaa)

If you find any issues or have some feedback, please raise the same on the GitHub repo.

If you want to contribute to the project, please follow the [CONTRIBUTING](./CONTRIBUTING.md) section.
If you want to contribute to the project, please follow the [CONTRIBUTING](./CONTRIBUTING.md)
section.

Do leave a thumbs up if you liked it.

Expand Down
2 changes: 2 additions & 0 deletions doc/widgets/avatar.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# AvatarMakerAvatar

![1608830483994](https://user-images.githubusercontent.com/37346450/103071632-009ec100-45ea-11eb-97c4-96c9ec67e204.gif)

This widget renders the avatar of the user on screen.

## Parameters
Expand Down
2 changes: 2 additions & 0 deletions doc/widgets/customizer.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# AvatarMakerCustomizer

![1608827561239](https://user-images.githubusercontent.com/37346450/154008536-a687828c-dc9d-4a62-aa11-b800d4fb7763.jpg)

This widget provides the user with a UI for customizing their Avatar_Maker

## Parameters
Expand Down
15 changes: 15 additions & 0 deletions doc/widgets/reset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# AvatarMakerResetWidget

![AvatarMakerResetWidget](https://github.com/RoadTripMoustache/avatar_maker/assets/36586573/ef052b05-1b23-4f77-8a47-1d08fc734958)

Renders a reset button by default OR can be used as a [InkWell] wrapper for the [child] widget. It resets the avatar to the last avatar saved state.

Additional callbacks may be triggered by passing a Function to [onTap].

## Parameters
- [theme] : AvatarMakerThemeData - Pass in your `theme` to customize the appearance of the default reset button.
- [onTap] : Function? - Additional callbacks to be triggered on tapping the widget after the reset operation is executed.
- [child] : Widget? - A widget to render as the child of a [InkWell]. If [null], then a default reset button is shown to the user.
- [splashFactory] : InteractiveInkFeatureFactory? - Defines the appearance of the splash.
- [splashColor] : Color? - The splash color of the ink response.
- [radius] : double? - The radius of the ink splash.
2 changes: 2 additions & 0 deletions doc/widgets/save.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# AvatarMakerSaveWidget

![1608827561239](https://user-images.githubusercontent.com/37346450/154008545-8325af7b-58a2-4419-8544-929ffbdbb9ff.jpg)

Renders a save button by default OR can be used as a [InkWell] wrapper for the [child] widget.

Additional callbacks may be triggered by passing a Function to [onTap].
Expand Down
1 change: 1 addition & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class NewPage extends StatelessWidget {
),
Spacer(),
AvatarMakerSaveWidget(),
AvatarMakerResetWidget(),
],
),
),
Expand Down
1 change: 1 addition & 0 deletions lib/avatar_maker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ export "src/core/models/theme_data.dart";
// Widgets
export "src/avatar/avatar_maker_avatar.dart";
export "src/customizer/avatar_maker_customizer.dart";
export "src/customizer/avatar_maker_reset_widget.dart";
export "src/customizer/avatar_maker_save_widget.dart";
11 changes: 11 additions & 0 deletions lib/src/customizer/avatar_maker_customizer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "package:avatar_maker/src/core/models/theme_data.dart";
import "package:avatar_maker/src/customizer/widgets/customizer_body.dart";
import "package:flutter/material.dart";
import "package:get/get.dart";
import "package:get/get_state_manager/src/simple/list_notifier.dart";

/// This widget provides the user with a UI for customizing their Avatar_Maker
///
Expand Down Expand Up @@ -69,6 +70,9 @@ class _AvatarMakerCustomizerState extends State<AvatarMakerCustomizer>
late int nbrDisplayedCategories;
late TabController tabController;

/// To easily dispose of the listener on the dispose of the widget.
Disposer? avatarMakerControllerListenerDisposer;

@override
void initState() {
super.initState();
Expand All @@ -89,10 +93,17 @@ class _AvatarMakerCustomizerState extends State<AvatarMakerCustomizer>
tabController.addListener(() {
setState(() {});
});
avatarMakerControllerListenerDisposer = _controller.addListener(() {
setState(() {});
});
}

@override
void dispose() {
// Dispose of the lister for the avatar maker controller.
if (avatarMakerControllerListenerDisposer != null) {
avatarMakerControllerListenerDisposer!();
}
// This ensures that unsaved edits are reverted
avatarMakerController.restoreState();
super.dispose();
Expand Down
73 changes: 73 additions & 0 deletions lib/src/customizer/avatar_maker_reset_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import "package:flutter/material.dart";
import "package:avatar_maker/src/core/controllers/avatar_maker_controller.dart";
import "package:avatar_maker/src/core/models/theme_data.dart";
import "package:get/get.dart";

/// Renders a reset button by default OR can be used as a [InkWell]
/// wrapper for the [child] widget.
/// It resets the avatar to the last avatar saved state.
///
/// Additional callbacks may be triggered by passing a
/// Function to [onTap].
class AvatarMakerResetWidget extends StatelessWidget {
/// Pass in your `theme` to customize the appearance of the default
/// reset button.
final AvatarMakerThemeData theme;

/// Additional callbacks to be triggered on tapping the widget
/// after the reset operation is executed.
/// *******
/// Example: \
/// You may pass a function that triggers a snackbar saying "Reset done!" on
/// the screen.
final VoidCallback? onTap;

/// A widget to render as the child of a [InkWell].
///
/// If [null], then a default reset button is shown to the user.
final Widget? child;

/// Find an instance of the [AvatarMakerController] to use
///
/// Note: This expects the controller to be added to `Get`
/// previously during runtime.
final avatarMakerController = Get.find<AvatarMakerController>();

/// Defines the appearance of the splash.
final InteractiveInkFeatureFactory? splashFactory;

/// The splash color of the ink response.
final Color? splashColor;

final double? radius;

AvatarMakerResetWidget({
Key? key,
AvatarMakerThemeData? theme,
this.onTap,
this.child,
this.splashFactory,
this.splashColor,
this.radius,
}) : theme = theme ?? AvatarMakerThemeData.defaultTheme,
super(key: key);

@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
avatarMakerController.restoreState();
if (onTap != null) onTap!();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (onTap != null) onTap!();
onTap?.call();

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more of a suggestion, depending on wether we use the same lint rules as Kana to kanji the linter will ask for it

},
splashFactory: splashFactory,
radius: radius,
splashColor: splashColor,
child: child == null
? Icon(
Icons.replay,
color: theme.iconColor,
)
: child,
);
}
}
139 changes: 139 additions & 0 deletions test/src/customizer/avatar_maker_reset_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import "package:avatar_maker/avatar_maker.dart";
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
import "package:get/get.dart";
import "package:mockito/annotations.dart";
import "package:mockito/mockito.dart";

import "../../helpers.dart";
@GenerateNiceMocks(
[MockSpec<AvatarMakerController>(), MockSpec<InternalFinalCallback>()])
import "avatar_maker_reset_widget_test.mocks.dart";

final avatarMakerControllerMock = MockAvatarMakerController();
final internalFinalCallbackMock = MockInternalFinalCallback();

void main() {
setUpAll(() async {
when(avatarMakerControllerMock.onStart)
.thenAnswer((_) => internalFinalCallbackMock);
when(avatarMakerControllerMock.onDelete)
.thenAnswer((_) => internalFinalCallbackMock);
when(avatarMakerControllerMock.restoreState()).thenAnswer((_) => null);

Get.put<AvatarMakerController>(avatarMakerControllerMock);
});

tearDownAll(() async {
Get.delete<AvatarMakerController>();
});

group("AvatarMakerResetWidget", () {
group("UI", () {
testWidgets("Default", (WidgetTester tester) async {
await tester.pumpMaterialApp(AvatarMakerResetWidget());

final inkWellConditions = isA<InkWell>()
.having((i) => i.radius, "Check radius", null)
.having((i) => i.splashFactory, "Check splashFactory", null)
.having((i) => i.splashColor, "Check splashColor", null);
final inkwell = find.byType(InkWell);
expect(inkwell, findsOneWidget);
expect(inkwell.evaluate().first.widget, inkWellConditions);

final iconConditions = isA<Icon>()
.having((i) => i.icon, "Check save icon", Icons.replay)
.having((i) => i.color, "Check save icon color",
AvatarMakerThemeData.defaultTheme.iconColor);
final icon = find.byType(Icon);
expect(icon, findsOneWidget);
expect(icon.evaluate().first.widget, iconConditions);

verifyNever(avatarMakerControllerMock.restoreState());
});
testWidgets("With radius", (WidgetTester tester) async {
final double radius = 12.9;
await tester.pumpMaterialApp(AvatarMakerResetWidget(
radius: radius,
));

final inkWellConditions = isA<InkWell>()
.having((i) => i.radius, "Check radius", radius)
.having((i) => i.splashFactory, "Check splashFactory", null)
.having((i) => i.splashColor, "Check splashColor", null);
final inkwell = find.byType(InkWell);
expect(inkwell, findsOneWidget);
expect(inkwell.evaluate().first.widget, inkWellConditions);

final iconConditions = isA<Icon>()
.having((i) => i.icon, "Check save icon", Icons.replay)
.having((i) => i.color, "Check save icon color",
AvatarMakerThemeData.defaultTheme.iconColor);
final icon = find.byType(Icon);
expect(icon, findsOneWidget);
expect(icon.evaluate().first.widget, iconConditions);

verifyNever(avatarMakerControllerMock.restoreState());
});
testWidgets("With splashColor", (WidgetTester tester) async {
final Color splashColor = Colors.green;
await tester.pumpMaterialApp(AvatarMakerResetWidget(
splashColor: splashColor,
));

final inkWellConditions = isA<InkWell>()
.having((i) => i.radius, "Check radius", null)
.having((i) => i.splashFactory, "Check splashFactory", null)
.having((i) => i.splashColor, "Check splashColor", splashColor);
final inkwell = find.byType(InkWell);
expect(inkwell, findsOneWidget);
expect(inkwell.evaluate().first.widget, inkWellConditions);

final iconConditions = isA<Icon>()
.having((i) => i.icon, "Check save icon", Icons.replay)
.having((i) => i.color, "Check save icon color",
AvatarMakerThemeData.defaultTheme.iconColor);
final icon = find.byType(Icon);
expect(icon, findsOneWidget);
expect(icon.evaluate().first.widget, iconConditions);

verifyNever(avatarMakerControllerMock.restoreState());
});
testWidgets("With custom theme", (WidgetTester tester) async {
await tester.pumpMaterialApp(AvatarMakerResetWidget(
theme: AvatarMakerThemeData(
iconColor: Colors.pink,
),
));

final inkWellConditions = isA<InkWell>()
.having((i) => i.radius, "Check radius", null)
.having((i) => i.splashFactory, "Check splashFactory", null)
.having((i) => i.splashColor, "Check splashColor", null);
final inkwell = find.byType(InkWell);
expect(inkwell, findsOneWidget);
expect(inkwell.evaluate().first.widget, inkWellConditions);

final iconConditions = isA<Icon>()
.having((i) => i.icon, "Check save icon", Icons.replay)
.having((i) => i.color, "Check save icon color", Colors.pink);
final icon = find.byType(Icon);
expect(icon, findsOneWidget);
expect(icon.evaluate().first.widget, iconConditions);

verifyNever(avatarMakerControllerMock.restoreState());
});
});
group("On tap InkWell", () {
testWidgets("Default", (WidgetTester tester) async {
await tester.pumpMaterialApp(AvatarMakerResetWidget());

final inkwell = find.byType(InkWell);
expect(inkwell, findsOneWidget);
await tester.tap(inkwell);

verify(avatarMakerControllerMock.restoreState()).called(1);
});
});
});
}
Loading