WT 1.1.13 Test Central bug fixes
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
const channelName = 'flutter.oddbit.id/facebook_app_events';
|
||||
|
||||
class FacebookAppEvents {
|
||||
static const _channel = MethodChannel(channelName);
|
||||
|
||||
// See: https://github.com/facebook/facebook-android-sdk/blob/master/facebook-core/src/main/java/com/facebook/appevents/AppEventsConstants.java
|
||||
static const eventNameActivatedApp = 'fb_mobile_activate_app';
|
||||
static const eventNameDeactivatedApp = 'fb_mobile_deactivate_app';
|
||||
static const eventNameCompletedRegistration = 'fb_mobile_complete_registration';
|
||||
static const eventNameViewedContent = 'fb_mobile_content_view';
|
||||
static const eventNameRated = 'fb_mobile_rate';
|
||||
static const eventNameInitiatedCheckout = 'fb_mobile_initiated_checkout';
|
||||
static const eventNameAddedToCart = 'fb_mobile_add_to_cart';
|
||||
static const eventNameAddedToWishlist = 'fb_mobile_add_to_wishlist';
|
||||
|
||||
static const _paramNameValueToSum = "_valueToSum";
|
||||
static const paramNameCurrency = "fb_currency";
|
||||
static const paramNameRegistrationMethod = "fb_registration_method";
|
||||
static const paramNamePaymentInfoAvailable = "fb_payment_info_available";
|
||||
static const paramNameNumItems = "fb_num_items";
|
||||
static const paramValueYes = "1";
|
||||
static const paramValueNo = "0";
|
||||
|
||||
/// Parameter key used to specify a generic content type/family for the logged event, e.g.
|
||||
/// "music", "photo", "video". Options to use will vary depending on the nature of the app.
|
||||
static const paramNameContentType = "fb_content_type";
|
||||
|
||||
/// Parameter key used to specify data for the one or more pieces of content being logged about.
|
||||
/// Data should be a JSON encoded string.
|
||||
/// Example:
|
||||
/// "[{\"id\": \"1234\", \"quantity\": 2, \"item_price\": 5.99}, {\"id\": \"5678\", \"quantity\": 1, \"item_price\": 9.99}]"
|
||||
static const paramNameContent = "fb_content";
|
||||
|
||||
/// Parameter key used to specify an ID for the specific piece of content being logged about.
|
||||
/// This could be an EAN, article identifier, etc., depending on the nature of the app.
|
||||
static const paramNameContentId = "fb_content_id";
|
||||
|
||||
/// Clears the current user data
|
||||
Future<void> clearUserData() {
|
||||
return _channel.invokeMethod<void>('clearUserData');
|
||||
}
|
||||
|
||||
/// Clears the currently set user id.
|
||||
Future<void> clearUserID() {
|
||||
return _channel.invokeMethod<void>('clearUserID');
|
||||
}
|
||||
|
||||
/// Explicitly flush any stored events to the server.
|
||||
Future<void> flush() {
|
||||
return _channel.invokeMethod<void>('flush');
|
||||
}
|
||||
|
||||
/// Returns the app ID this logger was configured to log to.
|
||||
Future<String?> getApplicationId() {
|
||||
return _channel.invokeMethod<String>('getApplicationId');
|
||||
}
|
||||
|
||||
Future<String?> getAnonymousId() {
|
||||
return _channel.invokeMethod<String>('getAnonymousId');
|
||||
}
|
||||
|
||||
/// Log an app event with the specified [name] and the supplied [parameters] value.
|
||||
Future<void> logEvent({
|
||||
required String name,
|
||||
Map<String, dynamic>? parameters,
|
||||
double? valueToSum,
|
||||
}) {
|
||||
final args = <String, dynamic>{
|
||||
'name': name,
|
||||
'parameters': parameters,
|
||||
_paramNameValueToSum: valueToSum,
|
||||
};
|
||||
|
||||
return _channel.invokeMethod<void>('logEvent', _filterOutNulls(args));
|
||||
}
|
||||
|
||||
/// Sets user data to associate with all app events.
|
||||
/// All user data are hashed and used to match Facebook user from this
|
||||
/// instance of an application. The user data will be persisted between
|
||||
/// application instances.
|
||||
Future<void> setUserData({
|
||||
String? email,
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
String? phone,
|
||||
String? dateOfBirth,
|
||||
String? gender,
|
||||
String? city,
|
||||
String? state,
|
||||
String? zip,
|
||||
String? country,
|
||||
}) {
|
||||
final args = <String, dynamic>{
|
||||
'email': email,
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'phone': phone,
|
||||
'dateOfBirth': dateOfBirth,
|
||||
'gender': gender,
|
||||
'city': city,
|
||||
'state': state,
|
||||
'zip': zip,
|
||||
'country': country,
|
||||
};
|
||||
|
||||
return _channel.invokeMethod<void>('setUserData', args);
|
||||
}
|
||||
|
||||
/// Logs an app event that tracks that the application was open via Push Notification.
|
||||
Future<void> logPushNotificationOpen({
|
||||
required Map<String, dynamic> payload,
|
||||
String? action,
|
||||
}) {
|
||||
final args = <String, dynamic>{
|
||||
'payload': payload,
|
||||
'action': action,
|
||||
};
|
||||
|
||||
return _channel.invokeMethod<void>('logPushNotificationOpen', args);
|
||||
}
|
||||
|
||||
/// Sets a user [id] to associate with all app events.
|
||||
/// This can be used to associate your own user id with the
|
||||
/// app events logged from this instance of an application.
|
||||
/// The user ID will be persisted between application instances.
|
||||
Future<void> setUserID(String id) {
|
||||
return _channel.invokeMethod<void>('setUserID', id);
|
||||
}
|
||||
|
||||
/// Update user properties as provided by a map of [parameters]
|
||||
Future<void> updateUserProperties({
|
||||
required Map<String, dynamic> parameters,
|
||||
String? applicationId,
|
||||
}) {
|
||||
final args = <String, dynamic>{
|
||||
'parameters': parameters,
|
||||
'applicationId': applicationId,
|
||||
};
|
||||
|
||||
return _channel.invokeMethod<void>('updateUserProperties', args);
|
||||
}
|
||||
|
||||
// Below are shorthand implementations of the predefined app event constants
|
||||
|
||||
/// Log this event when an app is being activated.
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameactivatedapp
|
||||
Future<void> logActivatedApp() {
|
||||
return logEvent(name: eventNameActivatedApp);
|
||||
}
|
||||
|
||||
/// Log this event when an app is being deactivated.
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnamedeactivatedapp
|
||||
Future<void> logDeactivatedApp() {
|
||||
return logEvent(name: eventNameDeactivatedApp);
|
||||
}
|
||||
|
||||
/// Log this event when the user has completed registration with the app.
|
||||
/// Parameter [registrationMethod] is used to specify the method the user has
|
||||
/// used to register for the app, e.g. "Facebook", "email", "Google", etc.
|
||||
/// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnamecompletedregistration
|
||||
Future<void> logCompletedRegistration({String? registrationMethod}) {
|
||||
return logEvent(
|
||||
name: eventNameCompletedRegistration,
|
||||
parameters: {
|
||||
paramNameRegistrationMethod: registrationMethod,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Log this event when the user has rated an item in the app.
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnamerated
|
||||
Future<void> logRated({double? valueToSum}) {
|
||||
return logEvent(
|
||||
name: eventNameRated,
|
||||
valueToSum: valueToSum,
|
||||
);
|
||||
}
|
||||
|
||||
/// Log this event when the user has viewed a form of content in the app.
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameviewedcontent
|
||||
Future<void> logViewContent({
|
||||
Map<String, dynamic>? content,
|
||||
String? id,
|
||||
String? type,
|
||||
String? currency,
|
||||
double? price,
|
||||
}) {
|
||||
return logEvent(
|
||||
name: eventNameViewedContent,
|
||||
parameters: {
|
||||
paramNameContent: content != null ? json.encode(content) : null,
|
||||
paramNameContentId: id,
|
||||
paramNameContentType: type,
|
||||
paramNameCurrency: currency,
|
||||
},
|
||||
valueToSum: price,
|
||||
);
|
||||
}
|
||||
|
||||
/// Log this event when the user has added item to cart
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameaddedtocart
|
||||
Future<void> logAddToCart({
|
||||
Map<String, dynamic>? content,
|
||||
@required String? id,
|
||||
@required String? type,
|
||||
@required String? currency,
|
||||
@required double? price,
|
||||
}) {
|
||||
return logEvent(
|
||||
name: eventNameAddedToCart,
|
||||
parameters: {
|
||||
paramNameContent: content != null ? json.encode(content) : null,
|
||||
paramNameContentId: id,
|
||||
paramNameContentType: type,
|
||||
paramNameCurrency: currency,
|
||||
},
|
||||
valueToSum: price,
|
||||
);
|
||||
}
|
||||
|
||||
/// Log this event when the user has added item to cart
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameaddedtowishlist
|
||||
Future<void> logAddToWishlist({
|
||||
Map<String, dynamic>? content,
|
||||
@required String? id,
|
||||
@required String? type,
|
||||
@required String? currency,
|
||||
@required double? price,
|
||||
}) {
|
||||
return logEvent(
|
||||
name: eventNameAddedToWishlist,
|
||||
parameters: {
|
||||
paramNameContent: content != null ? json.encode(content) : null,
|
||||
paramNameContentId: id,
|
||||
paramNameContentType: type,
|
||||
paramNameCurrency: currency,
|
||||
},
|
||||
valueToSum: price,
|
||||
);
|
||||
}
|
||||
|
||||
/// Re-enables auto logging of app events after user consent
|
||||
/// if disabled for GDPR-compliance.
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/app-events/gdpr-compliance
|
||||
Future<void> setAutoLogAppEventsEnabled(bool enabled) {
|
||||
return _channel.invokeMethod<void>('setAutoLogAppEventsEnabled', enabled);
|
||||
}
|
||||
|
||||
/// Set Data Processing Options
|
||||
/// This is needed for California Consumer Privacy Act (CCPA) compliance
|
||||
///
|
||||
/// See: https://developers.facebook.com/docs/marketing-apis/data-processing-options
|
||||
Future<void> setDataProcessingOptions(
|
||||
List<String> options, {
|
||||
int? country,
|
||||
int? state,
|
||||
}) {
|
||||
final args = <String, dynamic>{
|
||||
'options': options,
|
||||
'country': country,
|
||||
'state': state,
|
||||
};
|
||||
|
||||
return _channel.invokeMethod<void>('setDataProcessingOptions', args);
|
||||
}
|
||||
|
||||
Future<void> logPurchase({
|
||||
required double amount,
|
||||
required String currency,
|
||||
Map<String, dynamic>? parameters,
|
||||
}) {
|
||||
final args = <String, dynamic>{
|
||||
'amount': amount,
|
||||
'currency': currency,
|
||||
'parameters': parameters,
|
||||
};
|
||||
return _channel.invokeMethod<void>('logPurchase', _filterOutNulls(args));
|
||||
}
|
||||
|
||||
Future<void> logInitiatedCheckout({
|
||||
required double totalPrice,
|
||||
required String currency,
|
||||
required String contentType,
|
||||
required String contentId,
|
||||
required int numItems,
|
||||
bool paymentInfoAvailable = false,
|
||||
}) {
|
||||
return logEvent(
|
||||
name: eventNameInitiatedCheckout,
|
||||
valueToSum: totalPrice,
|
||||
parameters: {
|
||||
paramNameContentType: contentType,
|
||||
paramNameContentId: contentId,
|
||||
paramNameNumItems: numItems,
|
||||
paramNameCurrency: currency,
|
||||
paramNamePaymentInfoAvailable: paymentInfoAvailable ? paramValueYes : paramValueNo,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets the Advert Tracking propeety for iOS advert tracking
|
||||
/// an iOS 14+ feature, android should just return a success.
|
||||
Future<void> setAdvertiserTracking({
|
||||
required bool enabled,
|
||||
}) {
|
||||
final args = <String, dynamic>{'enabled': enabled};
|
||||
|
||||
return _channel.invokeMethod<void>('setAdvertiserTracking', args);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// PRIVATE METHODS BELOW HERE
|
||||
|
||||
/// Creates a new map containing all of the key/value pairs from [parameters]
|
||||
/// except those whose value is `null`.
|
||||
Map<String, dynamic> _filterOutNulls(Map<String, dynamic> parameters) {
|
||||
final Map<String, dynamic> filtered = <String, dynamic>{};
|
||||
parameters.forEach((String key, dynamic value) {
|
||||
if (value != null) {
|
||||
filtered[key] = value;
|
||||
}
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'wave.dart';
|
||||
|
||||
const double _twoPi = math.pi * 2.0;
|
||||
const double _epsilon = .001;
|
||||
const double _sweep = _twoPi - _epsilon;
|
||||
|
||||
class LiquidCircularProgressIndicator extends ProgressIndicator {
|
||||
///The width of the border, if this is set [borderColor] must also be set.
|
||||
final double borderWidth;
|
||||
|
||||
///The color of the border, if this is set [borderWidth] must also be set.
|
||||
final Color borderColor;
|
||||
|
||||
///The widget to show in the center of the progress indicator.
|
||||
final Widget? center;
|
||||
|
||||
///The direction the liquid travels.
|
||||
final Axis direction;
|
||||
|
||||
LiquidCircularProgressIndicator({
|
||||
Key? key,
|
||||
double value = 0.5,
|
||||
Color? backgroundColor,
|
||||
Animation<Color>? valueColor,
|
||||
required this.borderWidth,
|
||||
required this.borderColor,
|
||||
this.center,
|
||||
this.direction = Axis.vertical,
|
||||
}) : super(
|
||||
key: key,
|
||||
value: value,
|
||||
backgroundColor: backgroundColor,
|
||||
valueColor: valueColor,
|
||||
);
|
||||
|
||||
Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor;
|
||||
|
||||
Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _LiquidCircularProgressIndicatorState();
|
||||
}
|
||||
|
||||
class _LiquidCircularProgressIndicatorState extends State<LiquidCircularProgressIndicator> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipPath(
|
||||
clipper: _CircleClipper(),
|
||||
child: CustomPaint(
|
||||
painter: _CirclePainter(
|
||||
color: widget._getBackgroundColor(context),
|
||||
),
|
||||
foregroundPainter: _CircleBorderPainter(
|
||||
color: widget.borderColor,
|
||||
width: widget.borderWidth,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Wave(
|
||||
value: widget.value!,
|
||||
color: widget._getValueColor(context),
|
||||
direction: widget.direction,
|
||||
),
|
||||
if (widget.center != null) Center(child: widget.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CirclePainter extends CustomPainter {
|
||||
final Color color;
|
||||
|
||||
_CirclePainter({required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
canvas.drawArc(Offset.zero & size, 0, _sweep, false, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CirclePainter oldDelegate) => color != oldDelegate.color;
|
||||
}
|
||||
|
||||
class _CircleBorderPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double width;
|
||||
|
||||
_CircleBorderPainter({required this.color, required this.width});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final borderPaint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = width;
|
||||
final newSize = Size(size.width - width, size.height - width);
|
||||
canvas.drawArc(Offset(width / 2, width / 2) & newSize, 0, _sweep, false, borderPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CircleBorderPainter oldDelegate) => color != oldDelegate.color || width != oldDelegate.width;
|
||||
}
|
||||
|
||||
class _CircleClipper extends CustomClipper<Path> {
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
final path = Path()..addArc(Offset.zero & size, 0, _sweep);
|
||||
return path;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'wave.dart';
|
||||
|
||||
class LiquidCustomProgressIndicator extends ProgressIndicator {
|
||||
///The widget to show in the center of the progress indicator.
|
||||
final Widget? center;
|
||||
|
||||
///The direction the liquid travels.
|
||||
final Axis direction;
|
||||
|
||||
///The path used to draw the shape of the progress indicator. The size of the progress indicator is controlled by the bounds of this path.
|
||||
final Path shapePath;
|
||||
|
||||
LiquidCustomProgressIndicator({
|
||||
Key? key,
|
||||
double value = 0.5,
|
||||
Color? backgroundColor,
|
||||
Animation<Color>? valueColor,
|
||||
this.center,
|
||||
required this.direction,
|
||||
required this.shapePath,
|
||||
}) : super(
|
||||
key: key,
|
||||
value: value,
|
||||
backgroundColor: backgroundColor,
|
||||
valueColor: valueColor,
|
||||
);
|
||||
|
||||
Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor;
|
||||
|
||||
Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _LiquidCustomProgressIndicatorState();
|
||||
}
|
||||
|
||||
class _LiquidCustomProgressIndicatorState extends State<LiquidCustomProgressIndicator> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pathBounds = widget.shapePath.getBounds();
|
||||
return SizedBox(
|
||||
width: pathBounds.width + pathBounds.left,
|
||||
height: pathBounds.height + pathBounds.top,
|
||||
child: ClipPath(
|
||||
clipper: _CustomPathClipper(
|
||||
path: widget.shapePath,
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: _CustomPathPainter(
|
||||
color: widget._getBackgroundColor(context),
|
||||
path: widget.shapePath,
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Positioned.fill(
|
||||
left: pathBounds.left,
|
||||
top: pathBounds.top,
|
||||
child: Wave(
|
||||
value: widget.value!,
|
||||
color: widget._getValueColor(context),
|
||||
direction: widget.direction,
|
||||
),
|
||||
),
|
||||
if (widget.center != null) Center(child: widget.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomPathPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final Path path;
|
||||
|
||||
_CustomPathPainter({required this.color, required this.path});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CustomPathPainter oldDelegate) => color != oldDelegate.color || path != oldDelegate.path;
|
||||
}
|
||||
|
||||
class _CustomPathClipper extends CustomClipper<Path> {
|
||||
final Path path;
|
||||
|
||||
_CustomPathClipper({required this.path});
|
||||
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
return path;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'wave.dart';
|
||||
|
||||
class LiquidLinearProgressIndicator extends ProgressIndicator {
|
||||
///The width of the border, if this is set [borderColor] must also be set.
|
||||
final double? borderWidth;
|
||||
|
||||
///The color of the border, if this is set [borderWidth] must also be set.
|
||||
final Color? borderColor;
|
||||
|
||||
///The radius of the border.
|
||||
final double? borderRadius;
|
||||
|
||||
///The widget to show in the center of the progress indicator.
|
||||
final Widget? center;
|
||||
|
||||
///The direction the liquid travels.
|
||||
final Axis direction;
|
||||
|
||||
LiquidLinearProgressIndicator({
|
||||
Key? key,
|
||||
double value = 0.5,
|
||||
Color? backgroundColor,
|
||||
Animation<Color>? valueColor,
|
||||
this.borderWidth,
|
||||
this.borderColor,
|
||||
this.borderRadius,
|
||||
this.center,
|
||||
this.direction = Axis.horizontal,
|
||||
}) : super(
|
||||
key: key,
|
||||
value: value,
|
||||
backgroundColor: backgroundColor,
|
||||
valueColor: valueColor,
|
||||
) {
|
||||
if (borderWidth != null && borderColor == null || borderColor != null && borderWidth == null) {
|
||||
throw ArgumentError("borderWidth and borderColor should both be set.");
|
||||
}
|
||||
}
|
||||
|
||||
Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor;
|
||||
|
||||
Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _LiquidLinearProgressIndicatorState();
|
||||
}
|
||||
|
||||
class _LiquidLinearProgressIndicatorState extends State<LiquidLinearProgressIndicator> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipPath(
|
||||
clipper: _LinearClipper(
|
||||
radius: widget.borderRadius!,
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: _LinearPainter(
|
||||
color: widget._getBackgroundColor(context),
|
||||
radius: widget.borderRadius!,
|
||||
),
|
||||
foregroundPainter: _LinearBorderPainter(
|
||||
color: widget.borderColor!,
|
||||
width: widget.borderWidth!,
|
||||
radius: widget.borderRadius!,
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Wave(
|
||||
value: widget.value!,
|
||||
color: widget._getValueColor(context),
|
||||
direction: widget.direction,
|
||||
),
|
||||
if (widget.center != null) Center(child: widget.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LinearPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double radius;
|
||||
|
||||
_LinearPainter({required this.color, required this.radius});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, size.width, size.height),
|
||||
Radius.circular(radius),
|
||||
),
|
||||
paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_LinearPainter oldDelegate) => color != oldDelegate.color;
|
||||
}
|
||||
|
||||
class _LinearBorderPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double width;
|
||||
final double? radius;
|
||||
|
||||
_LinearBorderPainter({
|
||||
required this.color,
|
||||
required this.width,
|
||||
required this.radius,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = width;
|
||||
final alteredRadius = radius ?? 0;
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(width / 2, width / 2, size.width - width, size.height - width),
|
||||
Radius.circular(alteredRadius - width),
|
||||
),
|
||||
paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_LinearBorderPainter oldDelegate) =>
|
||||
color != oldDelegate.color || width != oldDelegate.width || radius != oldDelegate.radius;
|
||||
}
|
||||
|
||||
class _LinearClipper extends CustomClipper<Path> {
|
||||
final double? radius;
|
||||
|
||||
_LinearClipper({required this.radius});
|
||||
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
final path = Path()
|
||||
..addRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, size.width, size.height),
|
||||
Radius.circular(radius ?? 0),
|
||||
),
|
||||
);
|
||||
return path;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
library liquid_progress_indicator;
|
||||
|
||||
export 'liquid_circular_progress_indicator.dart';
|
||||
//export 'liquid_linear_progress_indicator.dart';
|
||||
//export 'liquid_custom_progress_indicator.dart';
|
||||
@@ -0,0 +1,114 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Wave extends StatefulWidget {
|
||||
final double value;
|
||||
final Color color;
|
||||
final Axis direction;
|
||||
|
||||
const Wave({
|
||||
Key? key,
|
||||
required this.value,
|
||||
required this.color,
|
||||
required this.direction,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_WaveState createState() => _WaveState();
|
||||
}
|
||||
|
||||
class _WaveState extends State<Wave> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(seconds: 2),
|
||||
);
|
||||
_animationController.repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
builder: (context, child) => ClipPath(
|
||||
child: Container(
|
||||
color: widget.color,
|
||||
),
|
||||
clipper: _WaveClipper(
|
||||
animationValue: _animationController.value,
|
||||
value: widget.value,
|
||||
direction: widget.direction,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WaveClipper extends CustomClipper<Path> {
|
||||
final double animationValue;
|
||||
final double value;
|
||||
final Axis direction;
|
||||
|
||||
_WaveClipper({
|
||||
required this.animationValue,
|
||||
required this.value,
|
||||
required this.direction,
|
||||
});
|
||||
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
if (direction == Axis.horizontal) {
|
||||
Path path = Path()
|
||||
..addPolygon(_generateHorizontalWavePath(size), false)
|
||||
..lineTo(0.0, size.height)
|
||||
..lineTo(0.0, 0.0)
|
||||
..close();
|
||||
return path;
|
||||
}
|
||||
|
||||
Path path = Path()
|
||||
..addPolygon(_generateVerticalWavePath(size), false)
|
||||
..lineTo(size.width, size.height)
|
||||
..lineTo(0.0, size.height)
|
||||
..close();
|
||||
return path;
|
||||
}
|
||||
|
||||
List<Offset> _generateHorizontalWavePath(Size size) {
|
||||
final waveList = <Offset>[];
|
||||
for (int i = -2; i <= size.height.toInt() + 2; i++) {
|
||||
final waveHeight = (size.width / 20);
|
||||
final dx = math.sin((animationValue * 360 - i) % 360 * (math.pi / 180)) * waveHeight + (size.width * value);
|
||||
waveList.add(Offset(dx, i.toDouble()));
|
||||
}
|
||||
return waveList;
|
||||
}
|
||||
|
||||
List<Offset> _generateVerticalWavePath(Size size) {
|
||||
final waveList = <Offset>[];
|
||||
for (int i = -2; i <= size.width.toInt() + 2; i++) {
|
||||
final waveHeight = (size.height / 20);
|
||||
final dy = math.sin((animationValue * 360 - i) % 360 * (math.pi / 180)) * waveHeight + (size.height - (size.height * value));
|
||||
waveList.add(Offset(i.toDouble(), dy));
|
||||
}
|
||||
return waveList;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(_WaveClipper oldClipper) => animationValue != oldClipper.animationValue;
|
||||
}
|
||||
Reference in New Issue
Block a user