Related
i am trying to check if the user in specific geofencing zone using the Easy geofencing package but every time i call the EasyGeofencing.startGeofenceService() and EasyGeofencing.getGeofenceStream() with new position ( pointedLatitude, pointedLongitude, radiusMeter) the service do not work and the console print "Parse value===> false" if any one know how i solve this problem please help me, i am stuck since a week.
this is my code
import 'dart:async';
import 'package:easy_geofencing/easy_geofencing.dart';
import 'package:geolocator/geolocator.dart';
import 'package:easy_geofencing/enums/geofence_status.dart';
import 'package:flutter/material.dart';
import 'package:flutter_application_1/domain/models/shop_model.dart';
import 'package:flutter_application_1/presentation/resources/assets_manager.dart';
import 'package:flutter_application_1/presentation/resources/color_manager.dart';
import 'package:flutter_application_1/presentation/resources/values_manager.dart';
import 'package:flutter_application_1/presentation/sidebars/cardWidget.dart';
import 'package:flutter_application_1/presentation/sidebars/profileSideBar.dart';
import 'package:flutter_application_1/services/shop_services.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
class ShopsDownBar extends StatefulWidget with ChangeNotifier {
ShopsDownBar({Key? key}) : super(key: key);
#override
State<ShopsDownBar> createState() => _ShopsDownBarState();
}
class _ShopsDownBarState extends State<ShopsDownBar>
with SingleTickerProviderStateMixin<ShopsDownBar> {
StreamSubscription<GeofenceStatus>? geofenceStatusStream;
StreamSubscription<EasyGeofencing>? easyGeofencingStream;
Geolocator geolocator = Geolocator();
String geofenceStatus = '';
bool isReady = false;
int raduis = 25;
double? longitude;
double? latitude;
Position? position;
late StreamController<bool> isOpenStreamController;
late Stream<bool> isOpenNStream;
late StreamSink<bool> isOpenNSink;
late AnimationController _animationControler;
List<Shops> shops = [];
getCurrentPosition() async {
position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
print("LOCATION => ${position?.toJson()}");
isReady = (position != null) ? true : false;
}
setLocation() async {
await getCurrentPosition();
print("POSITION => ${position!.toJson()}");
}
final _animationDuration = const Duration(milliseconds: 500);
setShops() {
ShopServices().fetchShops().then((value) {
setState(() {
if (value != null) {
for (int i = 0; i < value.length; i++) {
shops.add(Shops(
type: value[i].type,
shopStatus: value[i].shopStatus,
rewards: value[i].rewards,
id: value[i].id,
shopName: value[i].shopName,
shopAddress: value[i].shopAddress,
markerShop: value[i].markerShop,
createdAt: value[i].createdAt,
updatedAt: value[i].updatedAt,
));
}
}
});
});
}
void onIconPressed() {
final animationStatus = _animationControler.status;
final isAnimationDone = animationStatus == AnimationStatus.completed;
if (isAnimationDone) {
isOpenNSink.add(false);
_animationControler.reverse();
} else if (Provider.of<ProfileSideBar>(context, listen: false).isOpen ==
false) {
isOpenNSink.add(true);
_animationControler.forward();
}
}
#override
void initState() {
setShops();
getCurrentPosition();
if (isReady) {
print('jawna behi');
setState(() {
setLocation();
});
}
_animationControler =
AnimationController(vsync: this, duration: _animationDuration);
isOpenStreamController = PublishSubject<bool>();
isOpenNStream = isOpenStreamController.stream;
isOpenNSink = isOpenStreamController.sink;
super.initState();
}
#override
void dispose() {
_animationControler.dispose();
isOpenStreamController.close();
isOpenNSink.close();
EasyGeofencing.stopGeofenceService();
super.dispose();
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
var height = size.height;
var width = size.width;
return StreamBuilder<bool>(
initialData: false,
stream: isOpenNStream,
builder: (context, isOpenAsync) {
return AnimatedPositioned(
duration: _animationDuration,
top: isOpenAsync.data == false ? height * 0.91 : height * 0.24,
bottom: AppSize.s1_5,
right: AppSize.s1_5,
left: AppSize.s1_5,
child: Column(
children: [
Align(
child: GestureDetector(
onTap: () {
onIconPressed();
},
child: Container(
// alignment: Alignment.cen,
padding: const EdgeInsets.only(
left: AppMargin.m60,
right: AppMargin.m60,
top: AppMargin.m8,
),
child: Icon(
isOpenAsync.data == true
? Icons.close
: Icons.wallet_giftcard,
size: AppSize.s28,
color: ColorManager.primary,
),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(AppSize.s40),
topRight: Radius.circular(AppSize.s40)),
),
),
),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(
left: AppMargin.m16, right: AppMargin.m16),
height: height / 1.4,
width: width,
decoration: BoxDecoration(
color: ColorManager.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(AppSize.s28),
topRight: Radius.circular(AppSize.s28)),
),
child: shops.isNotEmpty
? ListView.builder(
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(
bottom: AppPadding.p8,
top: AppPadding.p18),
child: Giftcart(
ontap: () {
// print("starting geoFencing Service");
EasyGeofencing.startGeofenceService(
pointedLatitude: shops[index]
.markerShop
.locations
.latitude
.toString(),
pointedLongitude: shops[index]
.markerShop
.locations
.longitude
.toString(),
radiusMeter: raduis.toString(),
eventPeriodInSeconds: 5);
geofenceStatusStream ??=
EasyGeofencing.getGeofenceStream()!
.listen((GeofenceStatus? status) {
print(status.toString());
setState(() {
geofenceStatus = status.toString();
});
if (status.toString() ==
'GeofenceStatus.enter') {
print("entered");
} else {
print("not entered");
}
});
},
shopName: shops[index].shopName,
details: shops[index].shopAddress,
imagePath: ImageAssets.logo1,
),
);
},
itemCount: shops.length)
: Padding(
padding: const EdgeInsets.fromLTRB(
AppPadding.p100,
AppPadding.p100,
AppPadding.p100,
AppPadding.p200),
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
ColorManager.primary),
),
)),
),
],
),
);
});
}
}
giftCart widget
import 'package:flutter/material.dart';
import 'package:flutter_application_1/presentation/resources/color_manager.dart';
import 'package:flutter_application_1/presentation/resources/values_manager.dart';
//import 'package:flutter_application_1/presentation/resources/font_manager.dart';
class Giftcart extends StatelessWidget {
final String shopName;
final String details;
final String imagePath;
final void Function() ontap;
const Giftcart({
Key? key,
required this.ontap,
required this.shopName,
required this.details,
required this.imagePath,
}) : super(key: key);
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
var height = size.height;
var width = size.width;
return Center(
child: Container(
decoration: BoxDecoration(
color: ColorManager.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: ColorManager.grey,
offset: const Offset(0, 0),
blurRadius: 10,
),
],
),
height: height * 0.1,
width: width * 0.8,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
height: height * 0.15,
width: width * 0.15,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
),
child: Image.asset(imagePath)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: height * 0.02,
),
Text(
shopName,
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.end,
),
SizedBox(
height: height * 0.01,
),
Text(
details,
style: Theme.of(context).textTheme.bodyText1,
textAlign: TextAlign.start,
),
],
),
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: SizedBox(
height: height * 0.12,
width: width * 0.12,
child: FloatingActionButton(
backgroundColor: ColorManager.primary,
onPressed: ontap,
child: Icon(
Icons.card_giftcard,
size: AppSize.s18,
color: ColorManager.white,
)),
),
),
],
),
),
);
}
}
the Screen
the button on the left is for checking the geofenceStatus
this is the package doc
Add "geofenceStatusStream = null;" after stopGeofenceService as below:
setState(() {
EasyGeofencing.stopGeofenceService();
geofenceStatusStream!.cancel();
geofenceStatusStream = null;
});
When I hover the icon button and then hover to another position always in the InkWell region, I get this exception:
Error: Assertion failed:
../…/animation/animation_controller.dart:487
_ticker != null
"AnimationController.reverse() called after AnimationController.dispose()\nAnimationController methods should not be used after calling dispose."
at Object.throw_ [as throw] (http://localhost:38805/dart_sdk.js:5063:11)
at Object.assertFailed (http://localhost:38805/dart_sdk.js:4988:15)
at animation_controller.AnimationController.new.reverse (http://localhost:38805/packages/flutter/src/animation/animation_controller.dart.lib.js:305:42)
at internalCallback (http://localhost:38805/dart_sdk.js:26215:11)
This is the code:
return Material(
child: InkWell(
onTap: widget.onTap,
onHover: (bool isHoverIn) {
print("isHoverIN: $isHoverIn iscancel: $isCancelButtonVisible");
// (isHoverIn) {
if (isHoverIn != isCancelButtonVisible)
setState(() {
isCancelButtonVisible = isHoverIn;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Container(
height: boxSize,
width: boxSize,
decoration: BoxDecoration(
image: DecorationImage(
image: widget.imageData.image!.image,
fit: BoxFit.cover,
),
border: Border.all(
color: Colors.grey,
width: 5,
),
borderRadius: BorderRadius.circular(12),
),
child: isCancelButtonVisible
? Align(
alignment: Alignment.topRight,
child: IconButton(
iconSize: 20,
icon: const Icon(Icons.cancel),
tooltip: 'rimuovi',
onPressed: () => widget.onRemove(widget.position),
),
)
: null,
)),
),
);
Printing the hover actions from the IconButton and out the IconButton in the InkWell region:
isHoverIN: true iscancel: false
isHoverIN: false iscancel: true
isHoverIN: true iscancel: false
This is the full widget:
class _ProductMediaViewer extends StatefulWidget {
final int position;
final ProductVariantImage imageData;
final void Function(int, int) swap;
final void Function(int) onRemove;
final void Function() onTap;
final double boxSize;
const _ProductMediaViewer({
Key? key,
required this.position,
required this.imageData,
required this.swap,
required this.onRemove,
required this.onTap,
required this.boxSize,
}) : super(key: key);
#override
_ProductMediaViewerState createState() => _ProductMediaViewerState();
}
class _ProductMediaViewerState extends State<_ProductMediaViewer> {
bool isCancelButtonVisible = false;
Widget getMediaContentBox(bool isTransparent) {
final boxSize =
widget.position == -1 ? widget.boxSize * 2.5 : widget.boxSize;
return Material(
color: Colors.transparent,
child: Opacity(
opacity: isTransparent ? 0.45 : 1.0,
child: InkWell(
// TODO
onTap: widget.onTap,
onHover: (bool isHoverIn) {
print("isHoverIN: $isHoverIn iscancel: $isCancelButtonVisible");
// (isHoverIn) {
if (isHoverIn != isCancelButtonVisible)
setState(() {
isCancelButtonVisible = isHoverIn;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Container(
height: boxSize,
width: boxSize,
decoration: BoxDecoration(
image: DecorationImage(
image: widget.imageData.image!.image,
fit: BoxFit.cover,
),
border: Border.all(
color: Colors.grey,
width: 5,
),
borderRadius: BorderRadius.circular(12),
),
child: isCancelButtonVisible
? Align(
alignment: Alignment.topRight,
child: IconButton(
iconSize: 20,
icon: const Icon(Icons.cancel),
tooltip: 'rimuovi',
onPressed: () => widget.onRemove(widget.position),
),
)
: null,
)),
),
),
);
}
#override
Widget build(BuildContext context) {
final boxSize =
widget.position == -1 ? widget.boxSize * 2.5 : widget.boxSize;
return Draggable<int>(
onDragCompleted: () => isCancelButtonVisible = false,
maxSimultaneousDrags: 1,
data: widget.position,
child: DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return getMediaContentBox(false);
},
onAccept: (int draggablePosition) {
widget.swap(draggablePosition, widget.position);
print("draggpos: $draggablePosition");
print("pos: ${widget.position}");
},
),
feedback: getMediaContentBox(true),
childWhenDragging: Container(
height: boxSize,
width: boxSize,
color: Colors.grey[200],
),
);
}
}
You can use this widget
class HoverWidget extends StatefulWidget {
final Widget child;
final Widget hoverChild;
final Function(PointerEnterEvent event) onHover;
const HoverWidget(
{Key key,
#required this.child,
#required this.hoverChild,
#required this.onHover})
: assert(child != null && hoverChild != null && onHover != null),
super(key: key);
#override
_HoverWidgetState createState() => _HoverWidgetState();
}
class _HoverWidgetState extends State<HoverWidget> {
bool _isHover = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (event) {
setState(() {
_isHover = true;
});
widget.onHover(event);
},
onExit: (event) {
setState(() {
_isHover = false;
});
},
child: _isHover ? widget.hoverChild : widget.child,
);
}
}
I am new to Flutter and I am trying to create a draggable button to be able to accept or deny requests, I have used the Dtaggable Widget but it is giving me many problems, the last one is to limit the Dragable(button) to the container and therefore I've seen Dragabble is not designed for this and I should have used GestureDetector instead, but when I tried, I can't position the button in the center and let me take the distances well, if someone could give me a hand I would appreciate it.
class SliderButton extends StatefulWidget {
final double containerWidth;
final double containerHeight;
final Color containerColor;
final double buttonSize;
final Color buttonColor;
final Color textColor;
final double textSize;
final String leftText;
final String rightText;
final String textResultDeny;
final String textResultAccept;
final IconData icon;
final Color iconColor;
final Function(BuildContext context)? sliderPrimaryFunction;
final Function(BuildContext context)? sliderSecondaryFunction;
SliderButton(
{required this.containerWidth,
required this.containerHeight,
this.containerColor = Colors.black,
this.buttonSize = 50,
this.buttonColor = Colors.white,
this.rightText = 'Aceptar',
this.textResultAccept = 'Aceptado',
this.leftText = 'Denegar',
this.textResultDeny = 'Denegado',
this.sliderPrimaryFunction,
this.sliderSecondaryFunction,
this.textColor = Colors.white,
this.textSize = 24,
this.icon = Icons.add_circle_outline,
this.iconColor = Colors.black});
#override
_SliderButtonState createState() => _SliderButtonState();
}
class _SliderButtonState extends State<SliderButton> {
Offset position = Offset(0, 0);
bool started = false;
int switchOptions = 0;
Color? _containerColor = Colors.black;
#override
Widget build(BuildContext context) {
return Center(
child: sliderContainer(),
);
}
sliderContainer() => Container(
width: this.widget.containerWidth,
height: this.widget.containerHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.00),
color: _containerColor,
boxShadow: [
BoxShadow(
color: Colors.black,
offset: Offset(0.0, 1.0),
blurRadius: 6.0,
),
],
),
child: sliderContainerContent());
sliderContainerContent() {
if (switchOptions == 0) return Center(child: containerContent());
if (switchOptions == 1)
return textResult(this.widget.textResultAccept);
else if (switchOptions == 2) return textResult(this.widget.textResultDeny);
}
containerContent() => Container(
width: this.widget.containerWidth,
height: this.widget.containerHeight,
child: Row(
children: [
started == false
? primaryText(this.widget.leftText, Alignment.centerLeft)
: Container(),
Center(
child: Container(
child: Draggable(
axis: Axis.horizontal,
feedback: roundedButton(),
child: started == false ? roundedButton() : Container(),
onDragStarted: () => setState(() {
started = true;
}),
onDragUpdate: (details) => _sequentialColor(details),
onDragEnd: (details) => _onSlideDragUpdate(details),
),
),
),
started == false
? primaryText(this.widget.rightText, Alignment.centerRight)
: Container(),
],
),
);
roundedButton() => Align(
child: Container(
width: this.widget.buttonSize,
height: this.widget.buttonSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: this.widget.buttonColor,
boxShadow: [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 1.0),
blurRadius: 6.0,
),
],
),
child: Icon(this.widget.icon,
color: this.widget.iconColor, size: this.widget.buttonSize),
),
);
primaryText(String text, Alignment alignment) => Container(
alignment: alignment,
padding: EdgeInsets.all(14.0),
child: Text(text,
style: Theme.of(context)
.textTheme
.headline5
?.copyWith(color: Colors.white)),
);
textResult(String text) => Center(
child: Text(text,
style: Theme.of(context)
.textTheme
.headline3
?.copyWith(color: Colors.white)));
void _sequentialColor(DragUpdateDetails details) {
print(details.localPosition.dx);
var initColor = 200;
var algo = 240;
var algo2 = 200;
for (var i = details.localPosition.dx; i > algo; i++) {
setState(() {
_containerColor = Colors.green[initColor];
initColor += 100;
algo += 30;
});
}
for (var i = details.localPosition.dx; i < algo2; i--) {
setState(() {
_containerColor = Colors.red[initColor];
initColor += 100;
algo2 -= 30;
});
}
}
void _onSlideDragUpdate(DraggableDetails details) {
if (details.offset.distance > 470) {
setState(() {
switchOptions = 1;
_containerColor = Colors.lightGreen;
Future.delayed(const Duration(milliseconds: 500), () {
this.widget.sliderPrimaryFunction ?? Navigator.pop(context);
});
});
} else if (details.offset.distance < 400) {
setState(() {
switchOptions = 2;
_containerColor = Theme.of(context).errorColor;
Future.delayed(const Duration(milliseconds: 500), () {
this.widget.sliderPrimaryFunction ?? Navigator.pop(context);
});
});
} else
setState(() {
_containerColor = Theme.of(context).primaryColorDark;
started = false;
});
}
}
You can do this in this package.
https://pub.dev/packages/slider_button
Here is an example for you for the plugin. If you are having issue regarding the package. I will be happy to help you configure your own code
Center(child: SliderButton(
action: () {
///Do something here
Navigator.of(context).pop();
},
label: Text(
"Slide to cancel Event",
style: TextStyle(
color: Color(0xff4a4a4a), fontWeight: FontWeight.w500, fontSize: 17),
),
icon: Text(
"x",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w400,
fontSize: 44,
),
),
));
Thank you very much for the reply. I was trying to do it without the help of external packages, I ended up changing the Draggable for GestureDetector and it already works correctly.
Here I leave my solution in case it could be of help to someone.
import 'package:flutter/material.dart';
class SliderButton extends StatefulWidget {
final ValueChanged<double> valueChanged;
final double containerWidth;
final double containerHeight;
final Color containerColor;
final double buttonSize;
final Color buttonColor;
final Color textColor;
final double textSize;
final String leftText;
final String rightText;
final String textResultDeny;
final String textResultAccept;
final IconData icon;
final Color iconColor;
final Function(BuildContext context)? sliderPrimaryFunction;
final Function(BuildContext context)? sliderSecondaryFunction;
SliderButton({
required this.valueChanged,
required this.containerWidth,
required this.containerHeight,
this.containerColor = Colors.black,
this.buttonSize = 50,
this.buttonColor = Colors.white,
this.rightText = 'Aceptar',
this.textResultAccept = 'Aceptado',
this.leftText = 'Denegar',
this.textResultDeny = 'Denegado',
this.sliderPrimaryFunction,
this.sliderSecondaryFunction,
this.textColor = Colors.white,
this.textSize = 24,
this.icon = Icons.add_circle_outline,
this.iconColor = Colors.black,
});
#override
_SliderButtonState createState() => _SliderButtonState();
}
class _SliderButtonState extends State<SliderButton> {
ValueNotifier<double> valueListener = ValueNotifier(.0);
bool started = false;
int switchOptions = 0;
Color? _containerColor = Colors.black;
IconData _icon = Icons.lock;
#override
void initState() {
valueListener.addListener(notifyParent);
valueListener.value = 0.5;
super.initState();
}
void notifyParent() {
this.widget.valueChanged(valueListener.value);
}
#override
Widget build(BuildContext context) {
return Center(
child: sliderContainer(),
);
}
sliderContainer() => Stack(
children: [
Container(
width: this.widget.containerWidth,
height: this.widget.containerHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.00),
color: _containerColor,
),
child: sliderContainerContent(),
),
Positioned(bottom: 2, child: slider())
],
);
sliderContainerContent() {
if (switchOptions == 0) return Center(child: containerContent());
if (switchOptions == 1)
return textResult(this.widget.textResultAccept);
else if (switchOptions == 2) return textResult(this.widget.textResultDeny);
}
containerContent() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
started == false ? primaryText(this.widget.leftText) : Container(),
started == false ? primaryText(this.widget.rightText) : Container(),
],
);
primaryText(String text) => Container(
padding: EdgeInsets.all(14.0),
child: Text(text,
style: Theme.of(context)
.textTheme
.headline5
?.copyWith(color: Colors.white)),
);
textResult(String text) => Center(
child: Text(text,
style: Theme.of(context)
.textTheme
.headline3
?.copyWith(color: Colors.white)));
slider() => Container(
width: this.widget.containerWidth,
height: this.widget.containerHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.00),
color: Colors.transparent,
),
child: Builder(
builder: (context) {
final handle = gestureDetector();
return animatedBuilder(handle);
},
),
);
gestureDetector() => GestureDetector(
onHorizontalDragUpdate: _slideColor,
onHorizontalDragStart: (details) {
setState(() {
started = true;
});
},
onHorizontalDragEnd: _onSlideDragUpdate,
child: roundedButton(),
);
animatedBuilder(handle) => AnimatedBuilder(
animation: valueListener,
builder: (context, child) {
return AnimatedAlign(
duration:
Duration(milliseconds: valueListener.value == 0.5 ? 300 : 0),
alignment: Alignment(valueListener.value * 2 - 1, .5),
child: child,
);
},
child: handle,
);
roundedButton() => Container(
width: this.widget.buttonSize,
height: this.widget.buttonSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: this.widget.buttonColor,
boxShadow: [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 1.0),
blurRadius: 6.0,
),
],
),
child: Icon(_icon,
color: this.widget.iconColor, size: this.widget.buttonSize - 5),
);
void _slideColor(DragUpdateDetails details) {
valueListener.value =
(valueListener.value + details.delta.dx / this.widget.containerWidth)
.clamp(.0, 1.0);
var sliderColor = 200;
var slideRight = 0.5;
var slideLeft = 0.5;
var i = valueListener.value;
for (; i > slideRight;) {
setState(() {
this._containerColor = Colors.green[sliderColor];
sliderColor += 100;
slideRight += 0.1;
_icon = Icons.lock_open_sharp;
});
}
for (; i < slideLeft;) {
setState(() {
this._containerColor = Colors.red[sliderColor];
sliderColor += 100;
slideLeft -= 0.1;
_icon = Icons.lock_outline_sharp;
});
}
}
void _onSlideDragUpdate(DragEndDetails details) {
if (valueListener.value >= 0.9) {
valueListener.value = 1;
setState(() {
switchOptions = 1;
_containerColor = Colors.lightGreen;
Future.delayed(const Duration(milliseconds: 500), () {
this.widget.sliderPrimaryFunction ?? Navigator.pop(context);
});
});
} else if (valueListener.value <= 0.1) {
valueListener.value = 0;
setState(() {
switchOptions = 2;
_containerColor = Theme.of(context).errorColor;
Future.delayed(const Duration(milliseconds: 500), () {
this.widget.sliderPrimaryFunction ?? Navigator.pop(context);
});
});
} else {
valueListener.value = 0.5;
setState(() {
_containerColor = Theme.of(context).primaryColorDark;
_icon = Icons.lock;
started = false;
});
}
}
}
'''
it still needs some improves but its working.
I have a page where we have some pickup session when you select a pickup session at the bottom SwipeActionButton widget activate
now user can swipe right side and after swipe complete an async function execute which most time hit an api so if api result is success app goes to next page no problem here but if api result gave an error it shows a dialog
Press Ok and dialog pop but SwipeActionButton widget still show complete swipe how I can reset it.
code
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: NormalAppBar(
title: Text("Assign Requests"),
),
body: Consumer<PickupSessionProvider>(
builder: (context, provider, child) => Stack(
children: [
widget.requestIds.length == 0
? _requestsLoaded
? provider.unassignedRequestCount == 0
? Center(
child: Text("No requests.",
style: Theme.of(context).textTheme.headline6),
)
: _buildRequestsList(provider.unassignedRequests!)
: Center(
child: CircularProgressIndicator(),
)
: provider.pickupSessions!.length == 0
? Center(
child: Text("No active pickup session.",
style: Theme.of(context).textTheme.headline6),
)
: ListView(
padding: const EdgeInsets.only(bottom: 80),
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text(
"Select pickup session",
style: Theme.of(context).textTheme.headline4,
),
),
for (var pickupSession in provider.pickupSessions!)
_buildPickupSessionTile(pickupSession)
],
),
Positioned(
bottom: 0,
child: SwipeActionButton(
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 20),
onDone: (_selectRequestList && requestIds.length == 0) ||
(!_selectRequestList &&
_selectedPickupSessionId == null)
? null
: () async {
var result = await showDialog(
context: context,
builder: (context) => _ProgressDialog(
requestIds: requestIds,
pickupSessionId: _selectedPickupSessionId),
barrierDismissible: true);
if (result == true) Navigator.of(context).pop(true);
},
doneText: "Assign request",
disabledText: "Assign request",
infoText: "Swipe to assign request",
),
)
],
),
),
);
}
Custom SwipeActionButton widget
class SwipeActionButton extends StatefulWidget {
final double height;
final Color doneColor;
final Color swiperColor;
final Color textColor;
final String doneText;
final String disabledText;
final VoidCallback? onDone;
final String? infoText;
final double? width;
final Color? backgroundColor;
final EdgeInsetsGeometry margin;
SwipeActionButton({
Key? key,
this.height = 50,
this.doneColor = const Color(0xff44b31f),
this.swiperColor = const Color(0xff44b31f),
this.textColor = const Color(0xff44b31f),
required this.doneText,
required this.disabledText,
this.onDone,
this.infoText,
this.width,
this.backgroundColor,
this.margin = EdgeInsets.zero
}) : super(key: key);
#override
_SwipeActionButtonState createState() => _SwipeActionButtonState();
}
class _SwipeActionButtonState extends State<SwipeActionButton>
with SingleTickerProviderStateMixin {
double swipePercent = 0.0;
bool swipeDone = false;
bool isDisabled = false;
late Color backgroundColor;
late AnimationController _controller;
Animation<double>? _animation;
void initState() {
super.initState();
backgroundColor = widget.backgroundColor ?? Color(0xff3344b31f);
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {
swipePercent = _animation?.value ??0;
});
})
..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed && swipeDone) {
widget.onDone!();
}
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
_onDragStart(DragStartDetails details) {
_controller.reset();
swipePercent = 0.0;
}
_onDragUpdate(DragUpdateDetails details) {
setState(() {
swipePercent =
details.globalPosition.dx / MediaQuery.of(context).size.width;
if (swipePercent > 0.90) swipeDone = true;
});
}
_onDragEnd(DragEndDetails details) {
if (swipePercent > 0.90 || swipeDone) {
_animation =
Tween<double>(begin: swipePercent, end: 1).animate(_controller);
} else {
_animation =
Tween<double>(end: 0, begin: swipePercent).animate(_controller);
}
_controller.forward();
}
#override
Widget build(BuildContext context) {
isDisabled = widget.onDone == null;
double screenWidth = MediaQuery.of(context).size.width;
return Container(
alignment: Alignment.center,
margin: widget.margin,
width: screenWidth - widget.margin.horizontal,
child: Stack(
clipBehavior: Clip.hardEdge,
children: <Widget>[
Container(
height: widget.height,
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
borderRadius: BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
width: 1.5,
),
),
child: Center(
child: Text(widget.infoText ?? "",
style: Theme.of(context)
.textTheme
.subtitle1!
.copyWith(color: widget.textColor))),
),
Container(
width: isDisabled
? screenWidth
: lerpDouble(widget.height, screenWidth, swipePercent),
height: widget.height,
child: Center(
child: Opacity(
opacity: isDisabled ? 1 : lerpDouble(0, 1, swipePercent)!,
child: Text(
isDisabled ? widget.disabledText : widget.doneText,
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Colors.white,
),
textScaleFactor:
isDisabled ? 1 : lerpDouble(0, 1, swipePercent),
))),
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : null,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
/* border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
), */
gradient: isDisabled
? null
: LinearGradient(
begin: Alignment.center,
end: Alignment.centerRight,
colors: [
widget.doneColor,
swipeDone ? widget.doneColor : backgroundColor
])),
),
isDisabled
? Container()
: Positioned(
left: lerpDouble(
0, screenWidth -(15 +widget.margin.horizontal) - (widget.height * .9), swipePercent),
/* top: widget.height * .1,
bottom: widget.height * .1,
*/
child: AbsorbPointer(
absorbing: swipeDone,
child: GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
child: Opacity(
opacity: 1,
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
height: widget.height,
width: widget.height,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
),
boxShadow: swipeDone
? null
: [
BoxShadow(
color: Colors.black45,
blurRadius: 4)
],
color: swipeDone
? backgroundColor
: widget.swiperColor),
child: swipeDone
? Icon(
Icons.check,
size: 20,
color: Colors.white,
)
: Icon(
Icons.arrow_forward,
size: 20,
color: Colors.white,
),
))))),
],
),
);
}
}
You're asking how you can make the swipeButton reset in case the request doesn't return with a valid value.
The swipeButton's state is defined by its swipeDone and swipePercent variables. To achieve what you want you need to pass swipeDone as a parameter when constructing the widget.
class SwipeActionButton extends StatefulWidget {
// Make swipeDone a class variable for the widget
bool swipeDone;
final double height;
final Color doneColor;
final Color swiperColor;
final Color textColor;
final String doneText;
final String disabledText;
final VoidCallback? onDone;
final String? infoText;
final double? width;
final Color? backgroundColor;
final EdgeInsetsGeometry margin;
SwipeActionButton({
// Add it to the constructor
required this.swipeDone,
Key? key,
this.height = 50,
this.doneColor = const Color(0xff44b31f),
this.swiperColor = const Color(0xff44b31f),
this.textColor = const Color(0xff44b31f),
required this.doneText,
required this.disabledText,
this.onDone,
this.infoText,
this.width,
this.backgroundColor,
this.margin = EdgeInsets.zero,
}) : super(key: key);
#override
_SwipeActionButtonState createState() => _SwipeActionButtonState();
}
In _SwipeActionButtonState, delete bool swipeDone = false; and replace every swipeDone by widget.swipeDone.
You also need to reset the value of swipePercent.
You can do this by adding at the beginning of the swipeButton's build method :
if (widget.swipeDone == false && swipePercent > 0.9) swipePercent = 0;
Now you can declare the variable swipeDone in the parent widget state, pass it as a parameter and modify it whenever needed. For more clarity I give you an example with a simple widget that reset the swipe button when the floating action button is pressed.
Complete code :
import 'package:flutter/material.dart';
import 'dart:ui';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool swipeDone = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(),
body: SwipeActionButton(
disabledText: 'disabled',
doneText: 'doneText',
onDone: () {},
swipeDone: swipeDone,
),
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
swipeDone = false;
});
}),
),
);
}
}
class SwipeActionButton extends StatefulWidget {
// Make swipeDone a class variable for the widget
bool swipeDone;
final double height;
final Color doneColor;
final Color swiperColor;
final Color textColor;
final String doneText;
final String disabledText;
final VoidCallback? onDone;
final String? infoText;
final double? width;
final Color? backgroundColor;
final EdgeInsetsGeometry margin;
SwipeActionButton({
// Add it to the constructor
required this.swipeDone,
Key? key,
this.height = 50,
this.doneColor = const Color(0xff44b31f),
this.swiperColor = const Color(0xff44b31f),
this.textColor = const Color(0xff44b31f),
required this.doneText,
required this.disabledText,
this.onDone,
this.infoText,
this.width,
this.backgroundColor,
this.margin = EdgeInsets.zero,
}) : super(key: key);
#override
_SwipeActionButtonState createState() => _SwipeActionButtonState();
}
class _SwipeActionButtonState extends State<SwipeActionButton> with SingleTickerProviderStateMixin {
double swipePercent = 0.0;
bool isDisabled = false;
late Color backgroundColor;
late AnimationController _controller;
Animation<double>? _animation;
#override
void initState() {
super.initState();
backgroundColor = widget.backgroundColor ?? Color(0xff3344b31f);
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {
swipePercent = _animation?.value ?? 0;
});
})
..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed && widget.swipeDone) {
widget.onDone!();
}
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
_onDragStart(DragStartDetails details) {
_controller.reset();
swipePercent = 0.0;
}
_onDragUpdate(DragUpdateDetails details) {
setState(() {
swipePercent = details.globalPosition.dx / MediaQuery.of(context).size.width;
if (swipePercent > 0.90) widget.swipeDone = true;
});
}
_onDragEnd(DragEndDetails details) {
if (swipePercent > 0.90 || widget.swipeDone) {
_animation = Tween<double>(begin: swipePercent, end: 1).animate(_controller);
} else {
_animation = Tween<double>(end: 0, begin: swipePercent).animate(_controller);
}
_controller.forward();
}
#override
Widget build(BuildContext context) {
if (widget.swipeDone == false && swipePercent > 0.9) swipePercent = 0;
isDisabled = widget.onDone == null;
double screenWidth = MediaQuery.of(context).size.width;
return Container(
alignment: Alignment.center,
margin: widget.margin,
width: screenWidth - widget.margin.horizontal,
child: Stack(
clipBehavior: Clip.hardEdge,
children: <Widget>[
Container(
height: widget.height,
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
borderRadius: BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
width: 1.5,
),
),
child: Center(
child: Text(widget.infoText ?? "",
style: Theme.of(context).textTheme.subtitle1!.copyWith(color: widget.textColor))),
),
Container(
width: isDisabled ? screenWidth : lerpDouble(widget.height, screenWidth, swipePercent),
height: widget.height,
child: Center(
child: Opacity(
opacity: isDisabled ? 1 : lerpDouble(0, 1, swipePercent)!,
child: Text(
isDisabled ? widget.disabledText : widget.doneText,
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Colors.white,
),
textScaleFactor: isDisabled ? 1 : lerpDouble(0, 1, swipePercent),
))),
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : null,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
/* border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
), */
gradient: isDisabled
? null
: LinearGradient(
begin: Alignment.center,
end: Alignment.centerRight,
colors: [widget.doneColor, widget.swipeDone ? widget.doneColor : backgroundColor])),
),
isDisabled
? Container()
: Positioned(
left:
lerpDouble(0, screenWidth - (15 + widget.margin.horizontal) - (widget.height * .9), swipePercent),
/* top: widget.height * .1,
bottom: widget.height * .1,
*/
child: AbsorbPointer(
absorbing: widget.swipeDone,
child: GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
child: Opacity(
opacity: 1,
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
height: widget.height,
width: widget.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
),
boxShadow:
widget.swipeDone ? null : [BoxShadow(color: Colors.black45, blurRadius: 4)],
color: widget.swipeDone ? backgroundColor : widget.swiperColor),
child: widget.swipeDone
? Icon(
Icons.check,
size: 20,
color: Colors.white,
)
: Icon(
Icons.arrow_forward,
size: 20,
color: Colors.white,
),
))))),
],
),
);
}
}
I created some buttons for my flutter app menu and then I tried to give them some clicky animations.
I found a plug-in that was meant for me, so I edited the code from it but I got some problems.
As you can see from the images below, the buttons' shadows are cutted off, and I don't know how to fix them.
(I just need to know how to solve this little issue)
That’s the plug-in I edited:
import 'package:flutter/material.dart';
class AnimatedButton extends StatefulWidget {
final GestureTapCallback onPressed;
final Widget child;
final bool enabled;
final double padding;
final bool isCircular;
final Color color;
final Color shadowColor;
final double height;
final double width;
final ShadowDegree shadowDegree;
final int duration;
const AnimatedButton({
Key key,
#required this.onPressed,
#required this.child,
#required this.isCircular,
this.padding = 0.0,
this.enabled = true,
this.color = Colors.blue,
this.shadowColor = Colors.blueAccent,
this.height = 64,
this.shadowDegree = ShadowDegree.light,
this.width = 200,
this.duration = 70,
}) : assert(child != null),
super(key: key);
#override
_AnimatedButtonState createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton> {
static const Curve _curve = Curves.easeIn;
static const double _shadowHeight = 4;
double _position = 4;
#override
Widget build(BuildContext context) {
final double _height = widget.height - _shadowHeight;
return GestureDetector(
// width here is required for centering the button in parent
child: Container(
width: widget.width,
height: _height + _shadowHeight,
child: Stack(
children: <Widget>[
// Shadow
Positioned(
bottom: -8,
child: Container(
height: _height,
width: widget.width,
decoration: BoxDecoration(
color: widget.enabled
? widget.shadowColor
: darken(Colors.grey, widget.shadowDegree),
borderRadius: BorderRadius.all(
widget.isCircular ? Radius.circular(37) : Radius.circular(30)
)
)
)
),
// Button
AnimatedPositioned(
curve: _curve,
duration: Duration(milliseconds: widget.duration),
bottom: _position,
child: Container(
height: _height,
width: widget.width,
decoration: BoxDecoration(
color: widget.enabled ? widget.color : Colors.grey,
borderRadius: BorderRadius.all(
widget.isCircular ? Radius.circular(37) : Radius.circular(27)
)
),
child: Padding(
padding: EdgeInsets.only(top: widget.padding),
child: Center(
child: widget.child
),
)
)
)
]
)
),
onTapDown: widget.enabled ? _pressed : null,
onTapUp: widget.enabled ? _unPressedOnTapUp : null,
onTapCancel: widget.enabled ? _unPressed : null
);
}
void _pressed(_) {
setState(() {
_position = -4;
});
}
void _unPressedOnTapUp(_) => _unPressed();
// top
void _unPressed() {
setState(() {
_position = 4;
});
widget.onPressed();
}
}
Color darken(Color color, ShadowDegree degree) {
double amount = degree == ShadowDegree.dark ? 0.5 : 0.12;
final hsl = HSLColor.fromColor(color);
final hslDark = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
return hslDark.toColor();
}
enum ShadowDegree { light, dark }
And that is how a button is made:
AnimatedButton(
color: Color(0xff4388fc),
child: Text(
"LOCAL",
style: TextStyles().textStyleMenuButtonText()
),
height: 80,
width: 250,
shadowColor: Color(0xff2963d4),
isCircular: false,
padding: 6.0,
onPressed: () {
ModeSelectorDialog().getDialog(
context,
MediaQuery.of(context).size.height,
MediaQuery.of(context).size.width
);
}
),
images
I got 5 buttons, but their shadows are cutted off, and I don't know why
That's how I want them to be
Overwrite the default constructor of a Stack by setting the overflow
overflow: Overflow.visible
Your build method of the animated button should look like this:
Widget build(BuildContext context) {
final double _height = widget.height - _shadowHeight;
return GestureDetector(
// width here is required for centering the button in parent
child: Container(
width: widget.width,
height: _height + _shadowHeight,
child: Stack(overflow: Overflow.visible, children: <Widget>[
// Shadow
Positioned(
bottom: -8,
child: Container(
height: _height,
width: widget.width,
decoration: BoxDecoration(
color: widget.enabled
? widget.shadowColor
: darken(Colors.grey, widget.shadowDegree),
borderRadius: BorderRadius.all(widget.isCircular
? Radius.circular(37)
: Radius.circular(30))))),
// Button
AnimatedPositioned(
curve: _curve,
duration: Duration(milliseconds: widget.duration),
bottom: _position,
child: Container(
height: _height,
width: widget.width,
decoration: BoxDecoration(
color: widget.enabled ? widget.color : Colors.grey,
borderRadius: BorderRadius.all(widget.isCircular
? Radius.circular(37)
: Radius.circular(27))),
child: Padding(
padding: EdgeInsets.only(top: widget.padding),
child: Center(child: widget.child),
)))
])),
onTapDown: widget.enabled ? _pressed : null,
onTapUp: widget.enabled ? _unPressedOnTapUp : null,
onTapCancel: widget.enabled ? _unPressed : null);
}