Flutter - How to place card back in the stack after left swipe - flutter

I am using swipe_stack.
It pretty much serves the purpose for me except that I want to place the swiped card back at the bottom of the stack on left swipe. I don't know how to achieve this.
Here is the git for this repo:
SwipeStack
I have imported the repo in my project so I can make changes to it. However, I don't how to achieve it.
child: SwipeStack(
key: dashboardController.swipeKeyFlashDeals,
padding: const EdgeInsets.only(
top: 10,
bottom: 20,
left: 5,
right: 10,
),
children: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((int index) {
return SwiperItem(
builder: (SwiperPosition position, double progress) {
return FlipCard(
direction: FlipDirection.HORIZONTAL,
speed: 500,
onFlipDone: (status) {
print(status);
},
/// FRONT SIDE
front: Material(
elevation: 4,
borderRadius: const BorderRadius.all(Radius.circular(20)),
child: Stack(children: [
ā€¦.
]),
ā€¦.
///BACK SIDE
back: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 75,
sigmaY: 75,
),
child: ā€¦
))
}).toList(),
visibleCount: 3,
stackFrom: StackFrom.Right,
translationInterval: 10,
scaleInterval: 0.03,
onEnd: () => debugPrint("onEnd"),
onSwipe: (int index, SwiperPosition position) {
return debugPrint("onSwipe $index $position");
},
onRewind: (int index, SwiperPosition position) =>
debugPrint("onRewind $index $position"),
),

Have you considered adding a function to the onSwipe property that adds the child you are swiping, back to the first position of the SwipeStack children?
The best way should be to change the implementation of the SwipeStack itself. Instead of removing the child from the children's list add him back to the top of the list. And create a bool keepChildrenOnSwipeLeft to control that.

Related

How to make rating bar non clickable in flutter

How to make default flutter ratingbar non clickable ?
I have to disable rating option once user give feedback. How do I do it.
RatingBar(
itemSize: 35,
initialRating: 0,
glowColor: Colors.transparent,
direction: Axis.horizontal,
allowHalfRating: false,
tapOnlyMode: false,
itemCount: 5,
itemPadding: const EdgeInsets.symmetric(horizontal: 0.0),
ratingWidget: RatingWidget(
full: Image.asset(img_star_rating_fill, width: 25.w, height: 25.h),
// full: const Icon(Icons.star, color:yellow_FFC800),
half: Image.asset(img_star_rating_fill, width: 25.w, height: 25.h),
// half: const Icon(Icons.star_half, color:yellow_FFC800,),
empty:
Image.asset(img_star_rating_empty, width: 25.w, height: 25.h),
),
// empty: const Icon(Icons.star_outline, color:gray_868590,)),
onRatingUpdate: (value) {
setState(() {
_ratingValue = value;
printData(
'Rating to consultation booking ID', _ratingValue.toString());
controller
.callRateConsultationAPI(
widget.i,
controller.pastBookingList[widget.i].id.toString(),
value.toString())
.then((value) {
setState(() {
// ratingBar.setFocusable(false);
});
});
});
})
You can set ignoreGestures to true, like this:
RatingBar(
ignoreGestures: true, // <---- add this
itemSize: 35,
initialRating:0,
glowColor: Colors.transparent,
direction: Axis.horizontal,
allowHalfRating: false,
...
)
IgnorePointer(child:RatingWidget)

How to get the index value whilst not getting duplicates within ListView.builder?

I'm trying to show the week's budget spending & timeline only when the user has put in the spending information.
This is the expected result and all seems to be working nicely until I add in more than just one spending in each week.
Here's what happens:
The problem that I understand is that the ListView.builder gets the date that is between "Initial" and "End" and builds the widgets. So because there's 2 spending between those dates, then it builds 2 of those widgets. The problem is I just can't seem to figure out a way to show them without duplication.
Here's my code:
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: snapshot.data!.length,
padding: const EdgeInsets.only(bottom: 8),
itemBuilder: (context, index) {
final spending = snapshot.data![index];
DateTime spendingDate = DateTime.parse(spending.date);
var initial =
DateTime(initialDate.year, initialDate.month, initialDate.day - 1);
var end = DateTime(endDate.year, endDate.month, endDate.day + 1);
return spendingDate.isAfter(initial) && spendingDate.isBefore(end)
? Column(
children: [
WeekDivider(label: label, dateEstimation: dateEstimation),
WeeklySpendingStream(
color: color,
snapshot: snapshot,
initialDate: initialDate,
endDate: endDate,
),
],
)
: const SizedBox();
},
);
}
WeeklySpendingStreamCode:
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: snapshot.data!.length,
padding: const EdgeInsets.only(bottom: 8),
itemBuilder: (context, index) {
final spending = snapshot.data![index];
DateTime spendingDate = DateTime.parse(spending.date);
var initial =
DateTime(initialDate.year, initialDate.month, initialDate.day - 1);
var end = DateTime(endDate.year, endDate.month, endDate.day + 1);
if (spendingDate.isAfter(initial) && spendingDate.isBefore(end)) {
return SwipeActionCell(
editModeOffset: 0,
fullSwipeFactor: 0.50,
key: ObjectKey(snapshot.data![index]),
trailingActions: [
SwipeAction(
performsFirstActionWithFullSwipe: true,
color: Colors.transparent,
content: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.red,
),
child: getIconButton(Colors.red, IconlyBold.delete)),
onTap: (handler) async {
handler(true);
await Future.delayed(const Duration(milliseconds: 100));
snapshot.data!.removeAt(index);
SpendingDatabaseHelper.instance.removeMethod(spending.id!);
},
),
],
child: SpendingCard(
beneficiary: spending.beneficiary,
budgetSpent: currency.format(int.parse(spending.budgetSpent)),
date: DateFormat("dd-MM-yyyy")
.format(DateTime.parse(spending.date)),
colorValue: color,
),
);
} else {
return const SizedBox();
}
},
);
}
The output of snapshot.data:
[
{id: 8, budgetName: šŸ£ Food & Beverage, beneficiary: ddd, budgetSpent: 1, date: 2022-02-21},
{id: 7, budgetName: šŸ£ Food & Beverage, beneficiary: dfgvsd, budgetSpent: 1, date: 2022-02-14},
{id: 4, budgetName: šŸ£ Food & Beverage, beneficiary: ddd, budgetSpent: 1, date: 2022-02-11},
{id: 10, budgetName: šŸ£ Food & Beverage, beneficiary: ddd, budgetSpent: 1, date: 2022-02-11},
{id: 5, budgetName: šŸ£ Food & Beverage, beneficiary: asxasd, budgetSpent: 1, date: 2022-02-06}
]
Would really appreciate any suggestions/ideas of how this can be resolved.
Thanks in advance!
Nevermind, I somehow found the solution by getting the data using ".where" and checking whether it is empty or not. It worked nicely.
var initial = DateTime(initialDate.year, initialDate.month, initialDate.day - 1);
var end = DateTime(endDate.year, endDate.month, endDate.day + 1);
final spending = snapshot.data!.where((s) =>
DateTime.parse(s.date).isAfter(initial) &&
DateTime.parse(s.date).isBefore(end));
return spending.isNotEmpty ||
DateTime.now().isAfter(initial) && DateTime.now().isBefore(end)
? spending.isEmpty
? Column(
children: [
WeekDivider(label: label, dateEstimation: dateEstimation),
const Padding(
padding: EdgeInsets.only(top: 8, bottom: 16),
child: Text(
'No spending this week',
style: kCaption,
),
),
],
)
: ListView(
shrinkWrap: true,
controller: ScrollController(),
padding: const EdgeInsets.only(bottom: 8),
children: [
Column(
children: [
WeekDivider(label: label, dateEstimation: dateEstimation),
WeeklySpendingStream(
color: color,
snapshot: snapshot,
initialDate: initialDate,
endDate: endDate,
),
],
)
],
)
: const SizedBox();
}
I think your problem raised because of you just extract the database file without filtering so do this use .toSet() or assign the extracted data to a variable as a map or Set and use .toSet().

How to randomly position widgets in a layout

Lets say I want to randomly position the widgets in a specific layout, like in the image attached below, how could I achieve it?
I was thinking of using a wrap widget, but that did not quit work, because it is not randomizing the children in a line. My code until now
return Wrap(
spacing: 30,
children: [
buildprofile(),
buildprofile(),
buildprofile(),
buildprofile(),
],
);
buildprofile() {
return Column(
children: [
CircleAvatar(
radius: 64,
backgroundColor: Colors.pink,
child: (CircleAvatar(
radius: 62,
backgroundImage: NetworkImage(profilepic),
)),
),
SizedBox(
height: 10,
),
Text(
"Sivaram",
style: mystyle(16, Colors.black, FontWeight.w700),
)
],
);
}
You could use flutter_staggered_grid_view
StaggeredGridView.count(
crossAxisCount: 4,
children: List.generate(
3,
(index) => Center(
child: CircleAvatar(
radius: 64,
backgroundColor: Colors.pink,
),
)),
staggeredTiles: [
StaggeredTile.count(2, 2), // takes up 2 rows and 2 columns space
StaggeredTile.count(2, 1), // takes up 2 rows and 1 column
StaggeredTile.count(1, 2), // takes up 1 row and 2 column space
], // scatter them randomly
);
You can create class Person, and store profile name and image,
class Person {
String name;
String imageUrl;
}
and in your code can store all your persons in array
List<Person> persons = [Person(), Person(),....]
Wrap(
spacing: 30,
children: _children
);
List<Widget> _children {
List<Widget> _widgets = List<Widget>();
List<Persons> _randomList = persons.shuffle();
_randomList.forEach((person) {
_widgets.add(_buildProfile(person))
});
return _widgets;
}

Searchable SliverGrid Rendering Wrong Items

I have a SliverGrid. I have a search field. In my search field onChange event I have a function that searches my local sqlite db based on the keyword entered by the user returns the results and reassigns to a variable and calls notifyListeners(). Now my problem is for some weird reason whenever I search for an item the wrong item is rendered.
I checked the results from my functions by iterating over the list and logging the title and the overall count as well and the results were correct however my view always rendered the wrong items. Not sure how this is possible.
I also noticed something strange, whenever it rendered the wrong item and I went back to my code and hit save, triggering live reload, when I switched back to my emulator it now displayed the right item.
I have tried the release build on an actual phone and it's the same behaviour. Another weird thing is sometimes certain items will duplicate and show twice in my list while the user is typing.
This is my function that searches my sqlite db:
Future<List<Book>> searchBookshelf(String keyword) async {
try {
Database db = await _storageService.database;
final List<Map<String, dynamic>> rows = await db
.rawQuery("SELECT * FROM bookshelf WHERE title LIKE '%$keyword%'; ");
return rows.map((i) => Book.fromJson(i)).toList();
} catch (e) {
print(e);
return null;
}
}
This is my function that calls the above function from my viewmodel:
Future<void> getBooksByKeyword(String keyword) async {
books = await _bookService.searchBookshelf(keyword);
notifyListeners();
}
This is my actual view where i have the SliverGrid:
class BooksView extends ViewModelBuilderWidget<BooksViewModel> {
#override
bool get reactive => true;
#override
bool get createNewModelOnInsert => true;
#override
bool get disposeViewModel => true;
#override
void onViewModelReady(BooksViewModel vm) {
vm.initialise();
super.onViewModelReady(vm);
}
#override
Widget builder(BuildContext context, vm, Widget child) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height) / 4.3;
final double itemWidth = size.width / 3;
var heading = Container(
margin: EdgeInsets.only(top: 35),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Books',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900),
),
Text(
'Lorem ipsum dolor sit amet.',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 14),
),
],
),
),
);
var searchField = Container(
margin: EdgeInsets.only(top: 5, left: 15, bottom: 15, right: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15)),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 1.0,
spreadRadius: 0.0,
offset: Offset(2.0, 1.0), // shadow direction: bottom right
),
],
),
child: TextFormField(
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(
FlutterIcons.search_faw,
size: 18,
),
suffixIcon: Icon(
FlutterIcons.filter_fou,
size: 18,
),
hintText: 'Search...',
),
onChanged: (keyword) async {
await vm.getBooksByKeyword(keyword);
},
onFieldSubmitted: (keyword) async {},
),
);
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 1, right: 1),
child: LiquidPullToRefresh(
color: Colors.amber,
key: vm.refreshIndicatorKey, // key if you want to add
onRefresh: vm.refresh,
showChildOpacityTransition: true,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Column(
children: [
heading,
searchField,
],
),
),
SliverToBoxAdapter(
child: SpaceY(15),
),
SliverToBoxAdapter(
child: vm.books.length == 0
? Column(
children: [
Image.asset(
Images.manReading,
width: 250,
height: 250,
fit: BoxFit.contain,
),
Text('No books in your bookshelf,'),
Text('Grab a book from our bookstore.')
],
)
: SizedBox(),
),
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid.count(
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
crossAxisCount: 3,
children: vm.books
.map((book) => BookTile(book: book))
.toList(),
),
)
],
),
))));
}
#override
BooksViewModel viewModelBuilder(BuildContext context) =>
BooksViewModel();
}
Now the reason I am even using SliverGrid in the first place is because I have a search field and a title above the grid and I want all items to scroll along with the page, I didn't want just the list to be scrollable.
I believe this odd behavior can be attributed to you calling vm.getBooksByKeyword() in onChanged. As this is an async method, there is no guarantee that the last result returned will be the result for the final text in the TextFormField. The reason you see the correct results after a live reload is because the method is being called again with the full text currently in the TextFormField.
The quickest way to verify this is to move the function call to onFieldSubmitted or onEditingComplete and see if it behaves correctly.
If you require calling the function with every change to the text, you will need to add a listener to the controller and be sure to only make the call after input has stopped for a specified amount of time, using a Timer, like so:
final _controller = TextEditingController();
Timer _timer;
...
_controller.addListener(() {
_timer?.cancel();
if(_controller.text.isNotEmpty) {
// only call the search method if keyword text does not change for 300 ms
_timer = Timer(Duration(milliseconds: 300),
() => vm.getBooksByKeyword(_controller.text));
}
});
...
#override
void dispose() {
// DON'T FORGET TO DISPOSE OF THE TextEditingController
_controller.dispose();
super.dispose();
}
...
TextFormField(
controller: controller,
...
);
So I found the problem and the solution:
The widget tree is remembering the list items place and providing the
same viewmodel as it had originally. Not only that it also takes every
item that goes into index 0 and provides it with the same data that
was enclosed on the Construction of the object.
Taken from here.
So basically the solution was to add and set a key property for each list item generated:
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
),
delegate: SliverChildListDelegate(vm.books
.map((book) => BookTile(
key: Key(book.id.toString()), book: book))
.toList()),
),
)
And also here:
const BookTile({Key key, this.book}) : super(key: key, reactive: false);
My search works perfectly now. :)

How to create image slider with Dot Indicator in flutter?

Example image with dot indicator
I have multiple images each with their own redirect link. Currently this works fine at displaying using a list view build to display the images inside a gesture detector.
However, Iā€™d like to add a dot indicator to show which image is being viewed. How can I get the index of the image being displayed? Or increase / decrease a counter when swiping left or right.
You should post the code of what you have done so people can see what your exact problem is.
If you're using ListView.builder, you can get index from itemBuilder. Then create a variable to hold the value of that index when you interact with the list
int currentIndex;
itemCount: list.length,
itemBuilder: (context, index) {
currentIndex = index;
}
Then below the listView, add a custom dot indicator list.
Row(
children: [
for(int i = 0; i < list.length; i++)
Container(
height: 10, width: 10,
decoration: BoxDecoration(
color: i == currentIndex ? Colors.white : Colors.grey,
borderRadius: BorderRadius.circular(5)
)
)
]
)
You can get the index from itemBuilder. and change index by pass index in activeIndex parameter of AnimatedSmoothIndicator.
for smooth indicator smooth_page_indicator
AnimatedSmoothIndicator(
activeIndex: index,
count: images.length,
effect: ExpandingDotsEffect(
radius: 10,
dotWidth: 10,
dotHeight: 10,
activeDotColor: Colors.green,
expansionFactor: 4,
dotColor: Colors.green.withOpacity(0.17),
), // your preferred effect
onDotClicked: (index) {
pageViewController.animateToPage(
index,
duration: const Duration(milliseconds: 500),
curve: Curves.ease,
);
},
)
For handling pageview by clicking on dot
pageViewController.animateToPage(
index,
duration: const Duration(milliseconds: 500),
curve: Curves.ease,
);