diff --git a/example/android/.gitignore b/example/android/.gitignore
new file mode 100644
index 0000000..0a741cb
--- /dev/null
+++ b/example/android/.gitignore
@@ -0,0 +1,11 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index a9339df..c7663cf 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 27
+ compileSdkVersion 29
lintOptions {
disable 'InvalidPackage'
@@ -33,12 +33,11 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "com.example.sample"
+ applicationId "com.example.example"
minSdkVersion 16
- targetSdkVersion 27
+ targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -53,9 +52,3 @@ android {
flutter {
source '../..'
}
-
-dependencies {
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.2'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
-}
diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..c208884
--- /dev/null
+++ b/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 8124e1d..55ca830 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,12 +1,5 @@
-
-
-
-
+ package="com.example.example">
-
+
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
+
+
+
+
diff --git a/example/android/app/src/main/java/com/example/example/MainActivity.java b/example/android/app/src/main/java/com/example/example/MainActivity.java
new file mode 100644
index 0000000..59f336d
--- /dev/null
+++ b/example/android/app/src/main/java/com/example/example/MainActivity.java
@@ -0,0 +1,6 @@
+package com.example.example;
+
+import io.flutter.embedding.android.FlutterActivity;
+
+public class MainActivity extends FlutterActivity {
+}
diff --git a/example/android/app/src/main/java/com/example/sample/MainActivity.java b/example/android/app/src/main/java/com/example/sample/MainActivity.java
deleted file mode 100644
index cedd82b..0000000
--- a/example/android/app/src/main/java/com/example/sample/MainActivity.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.example.sample;
-
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
-
-public class MainActivity extends FlutterActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
- }
-}
diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
index 00fa441..1f83a33 100644
--- a/example/android/app/src/main/res/values/styles.xml
+++ b/example/android/app/src/main/res/values/styles.xml
@@ -1,8 +1,18 @@
+
+
+
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..c208884
--- /dev/null
+++ b/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/android/build.gradle b/example/android/build.gradle
index bb8a303..e0d7ae2 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -5,7 +5,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.2.1'
+ classpath 'com.android.tools.build:gradle:3.5.0'
}
}
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
index 8bd86f6..a673820 100644
--- a/example/android/gradle.properties
+++ b/example/android/gradle.properties
@@ -1 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
+android.enableR8=true
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index 2819f02..296b146 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
index 5a2f14f..44e62bc 100644
--- a/example/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -1,15 +1,11 @@
include ':app'
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
- pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
-}
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
-plugins.each { name, path ->
- def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
- include ":$name"
- project(":$name").projectDir = pluginDirectory
-}
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/example/lib/src/app.dart b/example/lib/src/app.dart
index c3ed6a2..7307025 100644
--- a/example/lib/src/app.dart
+++ b/example/lib/src/app.dart
@@ -1,3 +1,5 @@
+import 'dart:math';
+
import 'package:flutter/material.dart';
import 'package:progress_indicator_button/progress_button.dart';
@@ -16,6 +18,12 @@ class App extends StatelessWidget {
height: 60,
child: ProgressButton(
borderRadius: BorderRadius.all(Radius.circular(8)),
+ gradient: new LinearGradient(
+ colors: [
+ Colors.red,
+ Colors.blue,
+ ]
+ ),
strokeWidth: 2,
child: Text(
"Sample",
@@ -24,12 +32,20 @@ class App extends StatelessWidget {
fontSize: 24,
),
),
- onPressed: (AnimationController controller) {
- if (controller.isCompleted) {
- controller.reverse();
- } else {
- controller.forward();
- }
+ errorChild: const Icon(
+ Icons.close_sharp,
+ color: Colors.white,
+ ),
+ successChild: const Icon(
+ Icons.check_sharp,
+ color: Colors.white,
+ ),
+ onPressed: ( controller ) async {
+ await controller.loading();
+ await new Future.delayed( const Duration( seconds: 3 ) );
+ if ( Random.secure().nextBool() )
+ await controller.success();
+ else await controller.error();
},
),
),
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 4633ee3..c083842 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -10,7 +10,7 @@ description: An example for the progress button implementation.
version: 1.0.0+1
environment:
- sdk: ">=2.0.0-dev.68.0 <3.0.0"
+ sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart
deleted file mode 100644
index 036f6f2..0000000
--- a/example/test/widget_test.dart
+++ /dev/null
@@ -1,11 +0,0 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility that Flutter provides. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-
-void main() {
-
-}
diff --git a/lib/button_stagger_animation.dart b/lib/button_stagger_animation.dart
deleted file mode 100644
index 57ba1a7..0000000
--- a/lib/button_stagger_animation.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-import 'package:flutter/material.dart';
-
-class ButtonStaggerAnimation extends StatelessWidget {
- // Animation fields
- final AnimationController controller;
-
- // Display fields
- final Color color;
- final Color progressIndicatorColor;
- final double progressIndicatorSize;
- final BorderRadius borderRadius;
- final double strokeWidth;
- final Function(AnimationController) onPressed;
- final Widget child;
-
- ButtonStaggerAnimation({
- Key key,
- this.controller,
- this.color,
- this.progressIndicatorColor,
- this.progressIndicatorSize,
- this.borderRadius,
- this.onPressed,
- this.strokeWidth,
- this.child,
- }) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return LayoutBuilder(builder: _progressAnimatedBuilder);
- }
-
- Widget _buttonChild() {
- if (controller.isAnimating) {
- return Container();
- } else if (controller.isCompleted) {
- return OverflowBox(
- maxWidth: progressIndicatorSize,
- maxHeight: progressIndicatorSize,
- child: CircularProgressIndicator(
- strokeWidth: strokeWidth,
- valueColor: AlwaysStoppedAnimation(progressIndicatorColor),
- ),
- );
- }
- return child;
- }
-
- AnimatedBuilder _progressAnimatedBuilder(
- BuildContext context, BoxConstraints constraints) {
- var buttonHeight = (constraints.maxHeight != double.infinity)
- ? constraints.maxHeight
- : 60.0;
-
- var widthAnimation = Tween(
- begin: constraints.maxWidth,
- end: buttonHeight,
- ).animate(
- CurvedAnimation(
- parent: controller,
- curve: Curves.easeInOut,
- ),
- );
-
- var borderRadiusAnimation = Tween(
- begin: borderRadius,
- end: BorderRadius.all(Radius.circular(buttonHeight / 2.0)),
- ).animate(CurvedAnimation(
- parent: controller,
- curve: Curves.easeInOut,
- ));
-
- return AnimatedBuilder(
- animation: controller,
- builder: (context, child) {
- return SizedBox(
- height: buttonHeight,
- width: widthAnimation.value,
- child: RaisedButton(
- shape: RoundedRectangleBorder(
- borderRadius: borderRadiusAnimation.value,
- ),
- color: color,
- child: _buttonChild(),
- onPressed: () {
- this.onPressed(controller);
- },
- ),
- );
- },
- );
- }
-}
diff --git a/lib/progress_button.dart b/lib/progress_button.dart
index 7039236..b95a2cc 100644
--- a/lib/progress_button.dart
+++ b/lib/progress_button.dart
@@ -1,11 +1,23 @@
library progress_button;
import 'package:flutter/material.dart';
-import 'package:progress_indicator_button/button_stagger_animation.dart';
+import 'package:progress_indicator_button/raised_gradient_button.dart';
+
+enum ButtonState { idle, loading, success, error }
+
+class ProgressButtonController {
+ final _ProgressButtonState _state;
+ ProgressButtonController._( this._state );
+ Future loading() async => await _state.updateState( ButtonState.loading );
+ Future success([ final Duration delayBeforeIdle = const Duration( seconds: 3 ) ]) async => await _state.updateState( ButtonState.success, delayBeforeIdle );
+ Future error([ final Duration delayBeforeIdle = const Duration( seconds: 3 ) ]) async => await _state.updateState( ButtonState.error, delayBeforeIdle );
+}
class ProgressButton extends StatefulWidget {
/// The background color of the button.
final Color color;
+ /// The background gradient of the button.
+ final Gradient gradient;
/// The progress indicator color.
final Color progressIndicatorColor;
/// The size of the progress indicator.
@@ -21,34 +33,37 @@ class ProgressButton extends StatefulWidget {
///
/// This will grant access to its [AnimationController] so
/// that the animation can be controlled based on the need.
- final Function(AnimationController) onPressed;
+ final Function(ProgressButtonController) onPressed;
/// The child to display on the button.
- final Widget child;
+ final Widget child, successChild, errorChild;
ProgressButton({
@required this.child,
+ @required this.successChild,
+ @required this.errorChild,
@required this.onPressed,
+ this.gradient,
this.color = Colors.blue,
this.strokeWidth = 2,
this.progressIndicatorColor = Colors.white,
this.progressIndicatorSize = 30,
this.borderRadius = const BorderRadius.all(Radius.circular(0)),
- this.animationDuration = const Duration(milliseconds: 300),
+ this.animationDuration = const Duration(milliseconds: 500),
});
@override
- _ProgressButtonState createState() => _ProgressButtonState();
+ _ProgressButtonState createState() => new _ProgressButtonState();
}
-class _ProgressButtonState extends State
- with TickerProviderStateMixin {
- AnimationController _controller;
-
+class _ProgressButtonState extends State with TickerProviderStateMixin {
+ ProgressButtonController _controller;
+ AnimationController _animationController;
+ ButtonState _state = ButtonState.idle;
@override
void initState() {
super.initState();
-
- _controller = AnimationController(
+ _controller = new ProgressButtonController._( this );
+ _animationController = AnimationController(
duration: widget.animationDuration,
vsync: this,
);
@@ -56,23 +71,113 @@ class _ProgressButtonState extends State
@override
void dispose() {
- _controller.dispose();
+ _animationController.dispose();
super.dispose();
}
@override
- Widget build(BuildContext context) {
- return Center(
- child: ButtonStaggerAnimation(
- controller: _controller.view,
- color: widget.color,
- strokeWidth: widget.strokeWidth,
- progressIndicatorColor: widget.progressIndicatorColor,
- progressIndicatorSize: widget.progressIndicatorSize,
- borderRadius: widget.borderRadius,
- onPressed: widget.onPressed,
- child: widget.child,
+ Widget build(BuildContext context) => new Center(
+ child: new LayoutBuilder(builder: _progressAnimatedBuilder),
+ );
+
+ Widget _buttonChild() {
+ if ( _animationController.isAnimating ) return const SizedBox();
+ else if ( _animationController.isCompleted ) {
+ return new OverflowBox(
+ maxWidth: widget.progressIndicatorSize,
+ maxHeight: widget.progressIndicatorSize,
+ child: CircularProgressIndicator(
+ strokeWidth: widget.strokeWidth,
+ valueColor: AlwaysStoppedAnimation(widget.progressIndicatorColor),
+ ),
+ );
+ } else {
+ if ( _state == ButtonState.success )
+ return widget.successChild;
+ else if ( _state == ButtonState.error )
+ return widget.errorChild;
+ else return widget.child;
+ }
+ }
+
+ AnimatedBuilder _progressAnimatedBuilder( _, final BoxConstraints constraints) {
+ final buttonHeight = constraints.maxHeight != double.infinity ? constraints.maxHeight : 60.0;
+ final widthAnimation = new Tween(
+ begin: constraints.maxWidth,
+ end: buttonHeight,
+ ).animate(
+ new CurvedAnimation(
+ parent: _animationController,
+ curve: Curves.easeInOut,
+ ),
+ );
+ final borderRadiusAnimation = new Tween(
+ begin: widget.borderRadius,
+ end: new BorderRadius.all( new Radius.circular( buttonHeight / 2.0 ) ),
+ ).animate(
+ new CurvedAnimation(
+ parent: _animationController,
+ curve: Curves.easeInOut,
+ ),
+ );
+ return new AnimatedBuilder(
+ animation: _animationController,
+ builder: ( _, child ) => SizedBox(
+ height: buttonHeight,
+ width: widthAnimation.value,
+ child: widget.gradient == null ? RaisedButton(
+ shape: RoundedRectangleBorder(
+ borderRadius: borderRadiusAnimation.value,
+ ),
+ color: widget.color,
+ child: new AnimatedSwitcher(
+ duration: const Duration( milliseconds: 250, ),
+ child: _buttonChild(),
+ ),
+ onPressed: () {
+ if ( _animationController.isDismissed && _state == ButtonState.idle )
+ widget.onPressed(_controller);
+ },
+ ) : new RaisedGradientButton(
+ child: new AnimatedSwitcher(
+ duration: const Duration( milliseconds: 250, ),
+ child: _buttonChild(),
+ ),
+ gradient: widget.gradient,
+ borderRadius: new BorderRadius.only(
+ topLeft: borderRadiusAnimation.value.topLeft,
+ topRight: borderRadiusAnimation.value.topRight,
+ bottomRight: borderRadiusAnimation.value.bottomRight,
+ bottomLeft: borderRadiusAnimation.value.bottomLeft,
+ ),
+ height: buttonHeight,
+ width: widthAnimation.value,
+ onPressed: () {
+ if ( _animationController.isDismissed && _state == ButtonState.idle )
+ widget.onPressed(_controller);
+ },
+ ),
),
);
}
+
+ Future updateState( final ButtonState state, [ final Duration delayBeforeIdle = const Duration( seconds: 3 ) ] ) async {
+ if ( _animationController.isAnimating ) return;
+ else if ( _animationController.isCompleted && ( state == ButtonState.success || state == ButtonState.error ) ) {
+ await _animationController.reverse();
+ if ( mounted ) {
+ setState( () => _state = state );
+ new Future.delayed(
+ delayBeforeIdle,
+ () {
+ if ( mounted ) setState( () => _state = ButtonState.idle );
+ },
+ );
+ }
+ } else if ( mounted && state == ButtonState.loading ) {
+ setState( () => _state = state );
+ await _animationController.forward();
+ }
+ }
+
}
diff --git a/lib/raised_gradient_button.dart b/lib/raised_gradient_button.dart
new file mode 100644
index 0000000..b585013
--- /dev/null
+++ b/lib/raised_gradient_button.dart
@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+
+class RaisedGradientButton extends StatelessWidget {
+ final Widget child;
+ final Gradient gradient;
+ final double width;
+ final double height;
+ final BorderRadius borderRadius;
+ final List boxShadows;
+ final Function onPressed;
+
+ const RaisedGradientButton({
+ Key key,
+ @required this.child,
+ this.gradient,
+ this.width = double.infinity,
+ this.height = 60,
+ this.borderRadius,
+ this.boxShadows,
+ this.onPressed,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: width,
+ height: height,
+ decoration: BoxDecoration(
+ gradient: gradient,
+ borderRadius: borderRadius,
+ boxShadow: boxShadows,
+ ),
+ child: Material(
+ color: Colors.transparent,
+ child: InkWell(
+ onTap: onPressed,
+ child: Center(
+ child: child,
+ )),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/progress_button.iml b/progress_button.iml
index 8d48a06..97ae688 100644
--- a/progress_button.iml
+++ b/progress_button.iml
@@ -3,17 +3,16 @@
-
-
-
+
+
+
-
+
-
-
+
\ No newline at end of file
diff --git a/pubspec.yaml b/pubspec.yaml
index cbc0389..bd37d32 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -5,7 +5,7 @@ author: Pascal Brosinski
homepage: https://github.com/PascalAC/progress_button
environment:
- sdk: ">=2.0.0-dev.68.0 <3.0.0"
+ sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
diff --git a/test/progress_button_test.dart b/test/progress_button_test.dart
deleted file mode 100644
index 036913f..0000000
--- a/test/progress_button_test.dart
+++ /dev/null
@@ -1,18 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:progress_indicator_button/button_stagger_animation.dart';
-import 'package:progress_indicator_button/progress_button.dart';
-import '../example/lib/src/app.dart';
-
-void main() {
- testWidgets('ProgressButton - Start animation on click', (WidgetTester tester) async {
- await tester.pumpWidget(App());
-
- expect(find.byType(ProgressButton), findsOneWidget);
-
- await tester.tap(find.byType(RaisedButton));
-
- final widget = tester.widget(find.byType(ButtonStaggerAnimation));
- expect(widget.controller.isAnimating, true);
- });
-}