Related
i need to design screen like this in short videovideo_link
i did this but its not very similar to the previous effect
here i create CustomScrollView with NotificationListener to achieve snap effect but is still begging
and isused TransitionAppBar to make appbar
and used SliverPersistentHeader wiche implement SliverPersistentHeaderDelegate to controll on align size and position
import 'dart:math';
import 'dart:developer' as d;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class FinalScroll extends StatefulWidget {
FinalScroll({Key? key}) : super(key: key);
#override
State<FinalScroll> createState() => _FinalScrollState();
}
class _FinalScrollState extends State<FinalScroll> {
final ScrollController _controller = ScrollController();
final double kLength = 300;
final double kLengthSnap = 225;
final Duration kDurationSnap = const Duration(milliseconds: 20);
#override
Widget build(BuildContext context) {
if (!(!_controller.hasClients || _controller.offset == 0)) {
// print('controll offset ${_controller.offset}');
}
double sizeImage = kLength;
return SafeArea(
child: Scaffold(
body: LayoutBuilder(builder: (context, s) {
return NotificationListener(
onNotification: (scrollEndNotification) {
// print('controll offset ${_controller.offset}');
if (scrollEndNotification is ScrollEndNotification) {
if (_controller.offset < 0.4 * kLength) {
print('snap from CTB');
Future.microtask(
() {
return _controller.animateTo(0.0,
duration: kDurationSnap, curve: Curves.bounceInOut);
},
);
setState(() {
sizeImage = kLength;
});
}
if (_controller.offset > kLength * 0.4 &&
_controller.offset <= kLength * .6) {
print('snap from CTT');
Future.microtask(() => _controller.animateTo(kLength * 0.6,
duration: kDurationSnap, curve: Curves.bounceInOut));
setState(() {
sizeImage = 50;
});
}
}
return false;
},
child: CustomScrollView(
controller: _controller,
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
TransitionAppBar(
extent: kLength,
sizeImage: sizeImage,
avatar: const Text("Rancho"),
title: const Text(''),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text("${index}a"),
);
},
childCount: 25,
),
)
],
),
);
}),
),
);
}
}
class TransitionAppBar extends StatefulWidget {
final Widget avatar;
final Widget title;
final double extent;
final double sizeImage;
const TransitionAppBar({
required this.avatar,
required this.title,
this.extent = 250,
required this.sizeImage,
Key? key,
}) : super(key: key);
#override
State<TransitionAppBar> createState() => _TransitionAppBarState();
}
class _TransitionAppBarState extends State<TransitionAppBar>
with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return MediaQuery.removePadding(
context: context,
removeBottom: true,
child: SliverPersistentHeader(
pinned: true,
floating: true,
delegate: _TransitionAppBarDelegate(
avatar: widget.avatar,
sizeImage: widget.sizeImage,
title: widget.title,
extent: widget.extent > 200 ? widget.extent : 200,
vsync: this),
),
);
}
}
class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {
_TransitionAppBarDelegate(
{required this.avatar,
required this.title,
required this.sizeImage,
this.extent = 250,
required this.vsync});
final double sizeImage;
final Widget avatar;
final Widget title;
final double extent;
/// =================== init ========= ////
final _avatarMarginTween =
EdgeInsetsTween(begin: EdgeInsets.zero, end: const EdgeInsets.all(16));
///from BTC
final _titleMarginTweenBTC = EdgeInsetsTween(
begin: EdgeInsets.zero, end: const EdgeInsets.only(left: 70));
final _titleAlignTweenBTC = AlignmentTween(
begin: const Alignment(-.7, .8), end: const Alignment(-1.0, 0.2));
final _avatarAlignTweenBTC =
AlignmentTween(begin: const Alignment(-1, 1), end: Alignment.centerLeft);
///from CTT
final _titleMarginTweenCTT = EdgeInsetsTween(
begin: const EdgeInsets.only(left: 70), end: EdgeInsets.zero);
final _titleAlignTweenCTT = AlignmentTween(
begin: const Alignment(-1.0, 0.2), end: const Alignment(-.2, 0));
final _avatarAlignTweenCTT = AlignmentTween(
begin: Alignment.centerLeft, end: const Alignment(-.7, 0.0));
final decorationTween = DecorationTween(
begin: const BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.red,
image: DecorationImage(
fit: BoxFit.cover,
alignment: Alignment.centerLeft,
image: AssetImage('asset/dash.jpeg'))),
end: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.yellow,
image: DecorationImage(
alignment: Alignment.centerLeft,
fit: BoxFit.fill,
image: AssetImage(
'asset/dash.jpeg',
),
),
));
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final double topPadding = MediaQuery.of(context).padding.top;
final double visibleMainHeight = maxExtent - shrinkOffset - topPadding;
final double extraToolbarHeight =
max(minExtent - extent - topPadding - (kToolbarHeight), 0.0);
final double visibleToolbarHeight =
visibleMainHeight - extent - extraToolbarHeight;
final bool isScrolledUnder =
overlapsContent || (shrinkOffset > maxExtent - minExtent);
final double toolbarOpacity =
(visibleToolbarHeight / (kToolbarHeight)).clamp(0.0, 1.0);
final progress = min(1.0, shrinkOffset / maxExtent);
//margin
final avatarMargin = _avatarMarginTween.lerp(progress);
final titleMarginBTC = _titleMarginTweenBTC.lerp(progress);
final titleMarginCTT = _titleMarginTweenCTT.lerp(shrinkOffset / 300);
//align
final avatarAlignBTC = _avatarAlignTweenBTC.lerp(progress);
final titleAlignBTC = _titleAlignTweenBTC.lerp(progress);
final avatarAlignCTT = _avatarAlignTweenCTT.lerp(shrinkOffset / extent);
final titleAlignCTT = _titleAlignTweenCTT.lerp(shrinkOffset / extent);
//decoration
final decorationT = decorationTween.lerp(progress);
// print(
// 'shrinkOffset:$shrinkOffset maxExtent: $maxExtent minExtent: $minExtent progress : $progress');
print(sizeImage);
final Widget toolbar = Stack(
fit: StackFit.passthrough,
children: <Widget>[
AnimatedContainer(
duration: const Duration(milliseconds: 100),
height: minExtent,
constraints: BoxConstraints(maxHeight: minExtent),
color: Colors.grey.shade100,
),
LayoutBuilder(builder: (context, constraint) {
var checkIfProgressThanFromValue = progress < 0.3;
var checkLessThan1 = !(progress > 0.01);
return SizedBox(
height: sizeImage,
width: sizeImage==maxExtent?MediaQuery.of(context).size.width:30,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Flexible(
child: Padding(
padding: avatarMargin,
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
constraints: BoxConstraints(
maxHeight: maxExtent,
maxWidth: constraint.maxWidth,
minHeight: 0,
minWidth: 0),
height: checkLessThan1 ? maxExtent : 50,
width: checkLessThan1
? MediaQuery.of(context).size.width
: 30,
// decoration: decorationT,
child: OverflowBox(
maxWidth: MediaQuery.of(context).size.width,
child: SizedBox(
height: sizeImage,
width: checkLessThan1
? MediaQuery.of(context).size.width
: 30,
child: FittedBox(
fit: BoxFit.fitHeight,
alignment: shrinkOffset > minExtent
? avatarAlignCTT
: avatarAlignBTC,
child: ClipRRect(
borderRadius: checkLessThan1
? BorderRadius.zero
: BorderRadius.circular(
min(24, progress * 24)),
clipBehavior: Clip.antiAlias,
child: AnimatedSize(
duration: Duration(milliseconds: 100),
child: Image.asset(
'asset/dash.jpeg',
// alignment: shrinkOffset > minExtent
// ? avatarAlignCTT
// : avatarAlignBTC,
fit: BoxFit.fill,
height: sizeImage,
width: sizeImage == maxExtent
? MediaQuery.of(context).size.width
: 30,
// height: checkIfProgressThanFromValue
// ? maxExtent
// : 50,
// width: checkIfProgressThanFromValue
// ? MediaQuery.of(context).size.width
// : 50,
),
),
),
),
),
),
),
),
),
],
),
);
}),
_RenderTilte(
progress: progress,
titleMarginBTC: titleMarginBTC,
titleMarginCTT: titleMarginCTT,
titleAlignBTC: titleAlignBTC,
titleAlignCTT: titleAlignCTT)
],
);
final Widget appBar = FlexibleSpaceBar.createSettings(
minExtent: minExtent,
maxExtent: maxExtent,
currentExtent: max(minExtent, maxExtent - shrinkOffset),
child: toolbar,
);
return SafeArea(
child: LayoutBuilder(builder: (context, c) {
return SizedBox(
height: c.maxHeight,
width: c.maxWidth,
child: ClipRect(child: appBar));
}),
);
}
#override
double get maxExtent => extent;
#override
double get minExtent => max(kToolbarHeight, (maxExtent * .33));
#override
bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) {
return true;
}
#override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration(
stretchTriggerOffset: 150, onStretchTrigger: () async {});
#override
FloatingHeaderSnapConfiguration? get snapConfiguration =>
FloatingHeaderSnapConfiguration(
curve: Curves.bounceInOut,
duration: const Duration(milliseconds: 10));
#override
PersistentHeaderShowOnScreenConfiguration? get showOnScreenConfiguration =>
const PersistentHeaderShowOnScreenConfiguration();
#override
final TickerProvider vsync;
}
class _RenderTilte extends StatelessWidget {
const _RenderTilte({
Key? key,
required this.progress,
required this.titleMarginBTC,
required this.titleMarginCTT,
required this.titleAlignBTC,
required this.titleAlignCTT,
}) : super(key: key);
final double progress;
final EdgeInsets titleMarginBTC;
final EdgeInsets titleMarginCTT;
final Alignment titleAlignBTC;
final Alignment titleAlignCTT;
#override
Widget build(BuildContext context) {
return Padding(
padding: progress < 0.3 ? titleMarginBTC : titleMarginCTT,
child: Align(
alignment: progress < 0.3 ? titleAlignBTC : titleAlignCTT,
child: Opacity(
opacity: progress == 0 || progress == 1 ? 1 : progress * 0.01,
child: const Text(
'abd alazeez ',
),
),
),
);
}
}
here how to build the previous design and i dont know what the next step
I was able to show the pictures as in the video by taking advantage of Johannes Milke's video that I left the link of. But that's not all I want. I need a structure that looks like these images but shows the total number of users. I leave the image of exactly what I want and my related codes.
What I want to achieve; Creating a structure where I can write the total number of users
There are a few packages on pub dev but not what I wanted. Thank you
Video:Source Video
image i want to make:
My stacked widget:
import 'package:flutter/material.dart';
class StackedWidgets extends StatelessWidget {
final List<Widget> items;
final TextDirection direction;
final double size;
final double xShift;
const StackedWidgets({
Key? key,
required this.items,
this.direction = TextDirection.ltr,
this.size = 100,
this.xShift = 20,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final allItems = items
.asMap()
.map((index, item) {
final left = size - xShift;
final value = Container(
width: size,
height: size,
child: item,
margin: EdgeInsets.only(left: left * index),
);
return MapEntry(index, value);
})
.values
.toList();
return Stack(
children: direction == TextDirection.ltr
? allItems.reversed.toList()
: allItems,
);
}
}
Usage my stacked widget:
Widget buildStackedImages({
TextDirection direction = TextDirection.ltr,
}) {
final double size = 100;
final double xShift = 20;
final urlImages = [
'https://images.unsplash.com/photo-1554151228-14d9def656e4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=633&q=80',
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80',
'https://images.unsplash.com/photo-1616766098956-c81f12114571?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80',
];
final items = urlImages.map((urlImage) => buildImage(urlImage)).toList();
return StackedWidgets(
direction: direction,
items: items,
size: size,
xShift: xShift,
);
}
Widget buildImage(String urlImage) {
final double borderSize = 5;
return ClipOval(
child: Container(
padding: EdgeInsets.all(borderSize),
color: Colors.white,
child: ClipOval(
child: Image.network(
urlImage,
fit: BoxFit.cover,
),
),
),
);
}
Add a label also to this class
import 'package:flutter/material.dart';
class StackedWidgets extends StatelessWidget {
final List<Widget> items;
final TextDirection direction;
final double size;
final double xShift;
final String lable;
const StackedWidgets({
Key? key,
required this.items,
this.direction = TextDirection.ltr,
this.size = 100,
this.xShift = 20,
this.label = '',
}) : super(key: key);
#override
Widget build(BuildContext context) {
final allItems = items
.asMap()
.map((index, item) {
final left = size - xShift;
final value = Container(
width: size,
height: size,
child: item,
margin: EdgeInsets.only(left: left * index),
);
return MapEntry(index, value);
})
.values
.toList();
return Row(
children: [
Stack(
children: direction == TextDirection.ltr
? allItems.reversed.toList()
: allItems,
),
Text(label),
]
);
}
}
In items pass the number widget and label too
return StackedWidgets(
direction: direction,
items: [...items, Container(
width: 25,//you can also add padding if required
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.orange,
),
child: Text('+22'))],
size: size,
xShift: xShift,
label: "users are here",
);
I will prefer this way,
Run on dartPad
class InTEST extends StatefulWidget {
const InTEST({Key? key}) : super(key: key);
#override
State<InTEST> createState() => _InTESTState();
}
class _InTESTState extends State<InTEST> {
int maxRenderAvatar = 5;
int numberOfActiveUser = 33;
double size = 100;
double borderSize = 5;
Widget buildStackedImages({
TextDirection direction = TextDirection.ltr,
}) {
List<String> urlImages = List.filled(
numberOfActiveUser, //based on your list
'https://images.unsplash.com/photo-1554151228-14d9def656e4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=633&q=80');
List<Widget> items = [];
final renderItemCount = numberOfActiveUser > maxRenderAvatar
? maxRenderAvatar
: urlImages.length;
for (int i = 0; i < renderItemCount; i++) {
items.add(
Positioned(
left: (i * size * .8),
child: buildImage(
urlImages[i],
),
),
);
}
// add counter if urlImages.length > maxRenderAvatar
if (numberOfActiveUser > maxRenderAvatar) {
items.add(
Positioned(
left: maxRenderAvatar * size * .8,
child: Container(
width: size,
height: size,
padding: EdgeInsets.all(borderSize),
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 4),
color: Colors.amber,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
"+ ${urlImages.length - maxRenderAvatar}",
style: TextStyle(
fontSize: 23,
),
),
),
),
);
}
return SizedBox(
height: size + (borderSize * 2), //10 for borderSize
width: MediaQuery.of(context).size.width,
child: Stack(
children: items,
),
);
}
Widget buildImage(String urlImage) {
return ClipOval(
child: Container(
padding: EdgeInsets.all(borderSize),
color: Colors.white,
child: ClipOval(
child: Image.network(
urlImage,
width: size,
height: size,
fit: BoxFit.cover,
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Slider(
value: numberOfActiveUser.toDouble(),
min: 0,
max: 55,
onChanged: (v) {
numberOfActiveUser = v.toInt();
setState(() {});
}),
Slider(
value: maxRenderAvatar.toDouble(),
min: 0,
max: 42,
onChanged: (v) {
maxRenderAvatar = v.toInt();
setState(() {});
}),
buildStackedImages(),
],
),
);
}
}
I have an app and I built some helper widgets to be able to reuse.
so I created this Button Widget which can show a loading icon if pressed and the async request is not completed. This is working fine when the app is hot restarted but when I am changing anything in the app or switching theme it is throwing an error that says this:
LateInitializationError: Field 'btnState' has not been initialized
However, it is working fine when creating this same concept using GetX package for state management for the busy and disabled state of the Button Widget but it is not working when using the flutter native way.
I think when switching theme GetX re-initialize all the widgets in the widget tree but StatefulWidget does not initialize it.
If you want to see GetX approach you can see here Flutter MVC Button Widget
Here is my Button Widget code
import 'package:flutter/material.dart';
import '../helpers/ColorPalette.dart';
import '../helpers/TextStyl.dart';
import 'LoadingIcon.dart';
class Button extends StatefulWidget {
late final _ButtonState btnState;
final String label;
final void Function(_ButtonState)? onTap;
final bool outline;
final Widget? leading;
final Widget? loadingIcon;
final bool block;
final Color? backgroundColor;
final Color? color;
final bool flat;
Button({
Key? key,
required this.label,
this.onTap,
this.leading,
this.loadingIcon,
this.flat = false,
this.backgroundColor = kcPrimary,
this.color,
}) : outline = false,
block = false,
super(key: key);
#override
State<Button> createState() {
btnState = _ButtonState();
return btnState;
}
}
class _ButtonState extends State<Button> {
bool isBusy = false;
bool isDisabled = false;
setBusy(bool val) {
setState(() {
isBusy = val;
});
return widget.btnState;
}
setDisabled(bool val) {
setState(() {
isDisabled = val;
});
return widget.btnState;
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (!isBusy && !isDisabled) {
widget.onTap!(widget.btnState);
}
},
child: widget.block
? AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 20.0),
width: double.infinity,
alignment: Alignment.center,
decoration: !widget.outline
? BoxDecoration(
color: !isDisabled ? widget.backgroundColor : widget.backgroundColor?.withOpacity(0.5),
borderRadius: BorderRadius.circular(!widget.flat ? 8 : 0),
)
: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(!widget.flat ? 8 : 0),
border: Border.all(
color: !isDisabled ? widget.backgroundColor! : widget.backgroundColor!.withOpacity(0.5),
width: 1,
),
),
child: !isBusy
? Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.leading != null) widget.leading!,
if (widget.leading != null) SizedBox(width: 5),
Text(
widget.label,
style: TextStyl.button(context)?.copyWith(
fontWeight: !widget.outline ? FontWeight.bold : FontWeight.w400,
color: !widget.outline
? widget.color != null
? widget.color
: getContrastColor(widget.backgroundColor!)
: widget.backgroundColor,
),
),
],
)
: widget.loadingIcon != null
? SizedBox(height: 20, width: 20, child: widget.loadingIcon)
: LoadingIcon(
color: !widget.outline
? widget.color != null
? widget.color
: getContrastColor(widget.backgroundColor!)
: widget.backgroundColor,
height: 16,
),
)
: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 20.0),
alignment: Alignment.center,
decoration: !widget.outline
? BoxDecoration(
color: !isDisabled ? widget.backgroundColor! : widget.backgroundColor!.withOpacity(0.5),
borderRadius: BorderRadius.circular(!widget.flat ? 8 : 0),
border: Border.all(
color: widget.backgroundColor!,
width: 1,
),
)
: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(!widget.flat ? 8 : 0),
border: Border.all(
color: widget.backgroundColor!,
width: 1,
),
),
child: !isBusy
? Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.leading != null) widget.leading!,
if (widget.leading != null) SizedBox(width: 5),
Text(
widget.label,
style: TextStyl.button(context)?.copyWith(
fontWeight: !widget.outline ? FontWeight.bold : FontWeight.w400,
color: !widget.outline
? widget.color != null
? widget.color
: getContrastColor(widget.backgroundColor!)
: widget.backgroundColor!,
),
),
],
)
: widget.loadingIcon != null
? SizedBox(height: 20, width: 20, child: widget.loadingIcon)
: LoadingIcon(
color: !widget.outline
? widget.color != null
? widget.color
: getContrastColor(widget.backgroundColor!)
: widget.backgroundColor!,
height: 16,
),
),
],
),
);
}
}
And here is the code where I used the Button Widget:
Button(
key: UniqueKey(),
label: "Test",
onTap: (btn) async {
btn.setBusy(true);
await Future.delayed(2.seconds);
btn.setBusy(false);
},
),
If you find yourself trying to manually access/manipulate the private _State class of any StatefulWidget then its a sign that you need to find a better approach to whatever it is you're trying to do.
I would avoid stuff like this
late final _ButtonState btnState; // this
...
#override
State<Button> createState() {
btnState = _ButtonState(); // this
return btnState;
}
...
final void Function(_ButtonState)? onTap; // using private state as arguments
If you want to do what that repo is doing with GetX in a native Flutter way then here's one way to approach it using ValueNotifier
If everything in the button depends on the status of an isBusy and isDisabled bool, then I'd create a custom class that ValueNotifier will sync to.
class ButtonStatus {
ButtonStatus({required this.isBusy, required this.isDisabled});
final bool isBusy;
final bool isDisabled;
}
Then a static controller class with relevant methods with similar functionality to the Getx class in the repo in addition to the same thing you're currently passing in as a test.
class ButtonStatusController {
// status is what ValueNotifier listens to
static ValueNotifier<ButtonStatus> status = ValueNotifier(
ButtonStatus(isBusy: false, isDisabled: false),
);
// equivalent to the function you're passing in the button
static Future<void> testFunction() async {
final isDisabled = status.value.isDisabled;
status.value = ButtonStatus(isBusy: true, isDisabled: isDisabled);
await Future.delayed(Duration(seconds: 2));
status.value = ButtonStatus(isBusy: false, isDisabled: isDisabled);
}
// updating ButtonStatus
static void updateButtonStatus(
{required bool isBusy, required bool isDisabled}) {
status.value = ButtonStatus(isBusy: isBusy, isDisabled: isDisabled);
}
}
Then your Button gets wrapped in a ValueListenableBuilder<ButtonStatus> and can be stateless. It will rebuild based on changes to the ButtonStatus and the functionality won't be affected by any changes to the Theme of the app.
class Button extends StatelessWidget {
final String label;
final void Function() onTap;
final bool outline;
final Widget? leading;
final Widget? loadingIcon;
final bool block;
final Color? backgroundColor;
final Color? color;
final bool flat;
Button({
Key? key,
required this.label,
required this.onTap,
this.leading,
this.loadingIcon,
this.flat = false,
this.backgroundColor = kcPrimary,
this.color,
}) : outline = false,
block = false,
super(key: key);
#override
Widget build(BuildContext context) {
// wrapping in ValueListenableBuilder of type ButtonStatus
return ValueListenableBuilder<ButtonStatus>(
valueListenable: ButtonStatusController
.status, // listening to any updates in the ButtonStatusController class
builder: (_, status, __) => GestureDetector(
// status here is what all the conditional builds in this widget now depend on
onTap: () {
if (!status.isBusy && !status.isDisabled) {
onTap();
}
},
child: block
? AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 20.0),
width: double.infinity,
alignment: Alignment.center,
decoration: !outline
? BoxDecoration(
color: !status.isDisabled
? backgroundColor
: backgroundColor?.withOpacity(0.5),
borderRadius: BorderRadius.circular(!flat ? 8 : 0),
)
: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(!flat ? 8 : 0),
border: Border.all(
color: !status.isDisabled
? backgroundColor!
: backgroundColor!.withOpacity(0.5),
width: 1,
),
),
child: !status.isBusy
? Row(
mainAxisSize: MainAxisSize.min,
children: [
if (leading != null) leading!,
if (leading != null) SizedBox(width: 5),
Text(
label,
style: TextStyl.button(context)?.copyWith(
fontWeight:
!outline ? FontWeight.bold : FontWeight.w400,
color: !outline
? color != null
? color
: getContrastColor(backgroundColor!)
: backgroundColor,
),
),
],
)
: loadingIcon != null
? SizedBox(height: 20, width: 20, child: loadingIcon)
: LoadingIcon(
color: !outline
? color != null
? color
: getContrastColor(backgroundColor!)
: backgroundColor,
height: 16,
),
)
: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding:
EdgeInsets.symmetric(vertical: 12.0, horizontal: 20.0),
alignment: Alignment.center,
decoration: !outline
? BoxDecoration(
color: !status.isDisabled
? backgroundColor!
: backgroundColor!.withOpacity(0.5),
borderRadius: BorderRadius.circular(!flat ? 8 : 0),
border: Border.all(
color: backgroundColor!,
width: 1,
),
)
: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(!flat ? 8 : 0),
border: Border.all(
color: backgroundColor!,
width: 1,
),
),
child: !status.isBusy
? Row(
mainAxisSize: MainAxisSize.min,
children: [
if (leading != null) leading!,
if (leading != null) SizedBox(width: 5),
Text(
label,
style: TextStyl.button(context)?.copyWith(
fontWeight: !outline
? FontWeight.bold
: FontWeight.w400,
color: !outline
? color != null
? color
: getContrastColor(backgroundColor!)
: backgroundColor!,
),
),
],
)
: loadingIcon != null
? SizedBox(
height: 20, width: 20, child: loadingIcon)
: LoadingIcon(
color: !outline
? color != null
? color
: getContrastColor(backgroundColor!)
: backgroundColor!,
height: 16,
),
),
],
),
),
);
}
}
Besides that, I suggest reading up on refactoring widgets with large build methods because that one is a bit unruly. It could me made a lot easier to read by breaking it up into smaller StatelessWidgets.
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;
});
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,
),
))))),
],
),
);
}
}