Expanded tile with modal - flutter

i'm trying to make custom expanded tile that show and image as a modal if a child is clicked.
For example, if i click Hat no 1 i want that appear an image, if i click Hat no 2 i want that appear a different image, etc
How can i do it?
This is my list
_buildExpandableContent(Clothes clothes) {
List<Widget> columnContent = [];
for (String content in clothes.contents)
columnContent.add(
new ListTile(
title: new Text(content, style: new TextStyle(fontSize: 20.0),),
trailing: new Icon (Icons.keyboard_arrow_right, color: Colors.black, size: 30),
),
);
return columnContent;
}
}
class Clothes {
final String title;
List<String> contents = [];
Clothes(this.title, this.contents);
}
List<Clothes> clothes = <Clothes>[
new Clothes(
'Hats',
['Hat no. 1', 'Hat no. 2', 'Hat no. 3', 'Hat no. 4'],
),
];
This is body
body: ListView.builder(
itemCount: clothes.length,
itemBuilder: (context, i) {
return Container(
margin: EdgeInsets.only(top: 15),
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(17.0),
child: Column(
children: <Widget>[
Container(
color: Colors.blueAccent,
child: new ExpansionTile(
trailing: Icon (Icons.keyboard_arrow_down_rounded, color: Colors.white, size: 30),
title: new Text(clothes[i].title, style: new TextStyle(color: Colors.white, fontSize: 20,),),
children: <Widget>[
Container(
color:white,
child: new Column(
children: _buildExpandableContent(clothes[i]),
),
),
],
),
)
]
),
),
),
);
},
),

You can create a new class for the clothes items which has a content and an image source:
class Clothes {
final String title;
List<ClothesItem> items = [];
Clothes(this.title, this.items);
}
class ClothesItem {
String content;
String imgSrc;
ClothesItem(this.content, this.imgSrc);
}
List<Clothes> clothes = <Clothes>[
new Clothes(
'Hats',
[
ClothesItem('Hat no. 1', 'https://picsum.photos/200'),
ClothesItem('Hat no. 2', 'https://picsum.photos/200'),
ClothesItem('Hat no. 3', 'https://picsum.photos/200'),
ClothesItem('Hat no. 4', 'https://picsum.photos/200')
],
),
];
Then you need to modify the ListTiles and set showDialog as its onTap method, this dialog shows the corresponding image:
_buildExpandableContent(Clothes clothes) {
List<Widget> columnContent = [];
for (ClothesItem item in clothes.items)
columnContent.add(
new ListTile(
title: new Text(
item.content,
style: new TextStyle(fontSize: 20.0),
),
trailing: new Icon(Icons.keyboard_arrow_right,
color: Colors.black, size: 30),
onTap: () => showDialog(
context: context,
builder: (context) {
return AlertDialog(content: Image.network(item.imgSrc));
}),
),
);
return columnContent;
}

Related

How do I create a function that changes data on page so I don't have to create multiple different pages for each stall

I have to create multiple files for different stalls but it seems so wrong and I know there's a better way but I just don't know how. Is there a way to create something like a page builder that will let me create multiple pages with different information from a single file. The difficult part is to make the onTap function of the images send the user to the stall_page of the selected stall. I tried doing this by making a view attribute in which I create a page and manually import the page route. But that involves creating a stall_info and stall_page for every single stall.
Instead of creating stall1_page, stall2_page and so on, can I create a generic stall function that will use the same page but just change the data? I know that's LITERALLY the point of object oriented programming languages but I'm really new to them as you'll tell my previous stupid questions.
This is the homescreen dashboard
class GridDashboard extends StatelessWidget {
Item item1 = Item(
title: 'Tray blazers',
subtitle: 'Open',
event: 'by Chef Tracy',
img: 'assets/images/tray_blazers-cr.png',
view: stallPage,
);
Item item2 = Item(
title: 'Papa Rimz',
subtitle: 'Open',
event: '',
img: 'assets/images/papa_rimz.png',
view: papaRimzPage,
);
Item item3 = Item(
title: 'W SAUCE',
subtitle: 'Open',
event: '',
img: 'assets/images/w_sauce-removebg.png',
view: wSaucePage,
);
Item item4 = Item(
title: 'African Kitchen',
subtitle: 'Open',
event: '',
img: 'assets/images/cherry-kitchen.png',
view: africanKitchenPage,
);
Item item5 = Item(
title: 'Suya Craze',
subtitle: 'Open',
event: '',
img: 'assets/images/suya_craze.png',
view: suyaCrazePage,
);
Item item6 = Item(
title: 'Zulkys cafe',
subtitle: 'Open',
event: '',
img: 'assets/images/zulkys-removeb.png',
view: zulkysCafePage,
);
Item item7 = Item(
title: 'Street food',
subtitle: 'Open',
event: '',
img: 'assets/images/street_food--removebg-.png',
view: streetFoodPage,
);
#override
Widget build(BuildContext context) {
List<Item> myList = [
item1,
item2,
item3,
item4,
item5,
item6,
item7,
];
return Flexible(
child: GridView.count(
childAspectRatio: 1.0,
padding: const EdgeInsets.only(left: 16, right: 16),
crossAxisCount: 2,
crossAxisSpacing: 18,
mainAxisSpacing: 18,
children: myList.map(
(data) {
return Container(
decoration: BoxDecoration(
color: const Color(0xff453658),
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(data.view);
},
child: Image.asset(
data.img,
width: 90, //double.infinity
),
),
const SizedBox(height: 14),
Text(
data.title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
data.subtitle,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 10,
color: Colors.white38,
),
),
const SizedBox(height: 8),
// Text(
// data.event,
// style: const TextStyle(
// fontWeight: FontWeight.w600,
// fontSize: 11,
// color: Colors.white70,
// ),
// ),
],
),
);
},
).toList(),
),
);
}
}
class Item {
String title;
String subtitle;
String event;
String img;
String view;
Item({
required this.title,
required this.subtitle,
required this.event,
required this.img,
required this.view,
});
}
This is my stall_page:
class StallPage extends StatefulWidget {
const StallPage({super.key});
#override
State<StallPage> createState() => _StallPageState();
}
class _StallPageState extends State<StallPage> {
var selected = 0;
final pageController = PageController();
final stall = Stall.generateRestaurant1();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xff392850), //kBackground,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomAppBar(
Icons.arrow_back_ios_outlined,
Icons.search_outlined,
leftCallback: () => Navigator.of(context).pop(),
),
StallInfo(), //
FoodList(
selected,
(int index) {
setState(() {
selected = index;
});
pageController.jumpToPage(index);
},
stall,
),
Expanded(
child: FoodListView(
selected,
(int index) {
setState(() {
selected = index;
});
},
pageController,
stall,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 25),
height: 60,
child: SmoothPageIndicator(
controller: pageController,
count: stall.menu.length,
effect: CustomizableEffect(
dotDecoration: DotDecoration(
width: 8,
height: 8,
color: Colors.grey.withOpacity(0.5),
borderRadius: BorderRadius.circular(8),
),
activeDotDecoration: DotDecoration(
width: 10,
height: 10,
color: kBackground,
borderRadius: BorderRadius.circular(10),
dotBorder: const DotBorder(
color: kPrimaryColor,
padding: 2,
width: 2,
),
),
),
onDotClicked: (index) => pageController.jumpToPage(index),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: kPrimaryColor,
elevation: 2,
child: const Icon(
Icons.shopping_cart_outlined,
color: Colors.black,
size: 30,
),
),
);
}
}
This is my stall_info
class StallInfo extends StatelessWidget {
final stall = Stall.generateRestaurant1();
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
stall.name,
style: const TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Row(
children: [
Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.blueGrey.withOpacity(0.4),
borderRadius: BorderRadius.circular(5),
),
child: Text(
stall.label,
style: const TextStyle(
color: Colors.white,
),
)),
const SizedBox(
width: 10,
),
],
)
],
),
ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset(
stall.logoUrl,
width: 80,
),
),
],
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
stall.desc,
style: const TextStyle(fontSize: 16),
),
Row(
children: [
const Icon(
Icons.star_outline,
color: Colors.amber,
),
Text(
'${stall.score}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 15),
],
)
],
)
],
),
);
}
}
And this is stall
class Stall {
String name;
String label;
String logoUrl;
String desc;
num score;
Map<String, List<Food>> menu;
Stall(
this.name,
this.label,
this.logoUrl,
this.desc,
this.score,
this.menu,
);
static Stall generateRestaurant1() {
return Stall(
'Tray blazers',
'Restaurant',
'assets/images/tray_blazers.jpg',
'Tray Blazers by Chef Tracy',
4.5,
{
'Recommended': Food.generateRecommendedFoods1(),
'Popular': Food.generatePopularFoods1(),
'Smoothie': [],
'Rice': [],
},
);
}
}
If I understand the question correctly, you want to open the StallPage but show different values on the page depending on which image (pertaining to a given 'Stall') was selected on the previous page? I.e. clicking on item2 should open the StallPage with the restaurant title "Papa Rimz" etc.?
In that case, you can pass the argument to your new route builder via the onTap() function as a constructor parameter instead of calling Stall.generateRestaurant1() with hardcoded values in a given dart file.
StallInfo
Instead of getting your stall data inside the build method, you simply accept it as a required parameter for your widget. Now you have access to the data (title, ...) anywhere inside here.
class StallInfo extends StatelessWidget {
// Contains the stall object with its name, label, menu etc.
final Stall stall;
StallInfo({super.key, required this.stall});
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
...
),
);
}
}
HomeScreen
I'm a bit confused as to what the item list in your your home screen is for. Are these food items in a restaurant? Because if so, I think it would be much easier to save them inside the stall as a list of items and then use that list here:
List<Stall> _stalls = [...];
I'd like to note here that you hardcoded all the items by name and then, in your build method, added them to a list. Since you don't need their names anywhere, it would be just a little bit better to move the List<Stall> myList outside the build method and simply assign the objects directly (that is, before you add a real database):
class GridDashboard extends StatelessWidget {
List<Stall> _stalls = [
Stall('Tray blazers', ...),
Stall('Papa Rimz', ...),
];
#override
Widget build(BuildContext context) {
// do something with your stalls, onTap, pass the element directly
....
children: _stalls.map(
(data) {
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => StallPage(stall: data)
));
}
);
}),
}
}
If you use a builder function for your GridView (which you should if there can be a lot of stalls), in the onTap() you can instead call:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => StallPage(stall: _stalls.elementAt(index))
));
StallPage
This page will look something like this
class StallPage extends StatefulWidget {
final Stall stall; // Take in the stall you passed from your home screen
const StallPage({super.key, required this.stall});
#override
State<StallPage> createState() => _StallPageState();
}
class _StallPageState extends State<StallPage> {
var selected = 0;
final pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
...
StallInfo(stall: widget.stall), // This is how you can access the values passed inside a StatefulWidget
...
);
}
}

How do I prevent an endless Listview scroll of product data being repeated on my product screens? [Flutter]

I'm working on a shopping app for a school project, and I'm trying to get my product images and details to show up on the various product detail screens. I used some sample images from flutter's Shrine sample app as I followed their tutorial. The problem I'm facing now is an endless Listview scroll that looks like this, and when you keep scrolling, it repeats the image and details endlessly:
What should I do to avoid this? Sorry, I am really new to coding so I am not to sure how to go about fixing this problem...Below I have included a few dart files of my current code which might be helpful. Thank you to anyone who is willing to help, it is very much appreciated!
Product detail screen dart file:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:MyShoppingApp/provider/CartProvider.dart';
import 'package:MyShoppingApp/db/cart_database.dart';
import 'package:MyShoppingApp/model/cart.dart';
import 'model/products_repository.dart';
import '../model/cart.dart';
class ProductDetailsPage extends StatelessWidget {
static const routeName = '/user-products';
ProductDetailsPage({Key? key}) : super(key: key); //const
DBHelper dbHelper = DBHelper();
#override
Widget build(BuildContext context) {
//get particular productId using the ModalRoute class
final productId = ModalRoute.of(context)!.settings.arguments as String;
print(productId);
//use Provider package to find out ID by accessing method declared in Product()
final loadedProduct = ProductsRepository().findById(productId);
//List<bool> clicked = List.generate(10, (index) => false, growable: true);
final cart = Provider.of<CartProvider>(context);
void saveData(int index) {
dbHelper
.insert(
CartItem(
id: index,
title: loadedProduct.name,
price: loadedProduct.price.toDouble(),
quantity: ValueNotifier(1),
image: loadedProduct.image,
),
)
.then((value) {
cart.addTotalPrice(loadedProduct.price.toDouble());
cart.addCounter();
print('Product Added to cart');
}).onError((error, stackTrace) {
print(error.toString());
});
}
return Scaffold(
backgroundColor: Colors.orange[50],
appBar: AppBar(
backgroundColor: Colors.deepOrange[900],
title: const Text("Product details "),
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_outlined,
color: Colors.black,
semanticLabel: 'back to home',
),
onPressed: () {
Navigator.pop(context);
},
),
),
//body:
body: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 8.0),
shrinkWrap: true,
itemCount: loadedProduct.length,
itemBuilder: (context, index) {
return Card(
//SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(
height: 300,
width: double.infinity,
child: Image.asset(
loadedProduct.image,
fit: BoxFit.cover,
),
),
const SizedBox(height: 10),
Text(
'\$${loadedProduct.price}',
style: const TextStyle(
color: Colors.grey,
fontSize: 20,
),
),
const SizedBox(
height: 10,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.blueGrey.shade900),
onPressed: () {
saveData(Random().nextInt(1000));
},
child: const Text('Add to Cart')),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
width: double.infinity,
child: Text(
loadedProduct.description,
textAlign: TextAlign.center,
softWrap: true,
),
),
],
),
);
})
);
}
}
Product Repository Dart file:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:MyShoppingApp/db/cart_database.dart';
//add product data
import 'package:MyShoppingApp/model/product.dart';
//to get all products at once or any particular product by its ID
//product class that uses mixins with ChangeNotifier
class ProductsRepository with ChangeNotifier {
DBHelper dbHelper = DBHelper();
static List<Product> loadProducts(Category category) {
//linked list storing objects of type Product
var allProducts = <Product>[
Product(
category: Category.accessories,
id: "0",
isFeatured: true,
name: 'Vagabond sack',
price: 120,
details: "Nice fancy shirt",
description: "Comfortable and minimalistic",
image: "packages/shrine_images/0-0.jpg",
),
Product(
category: Category.accessories,
id: "1",
isFeatured: true,
name: 'Stella sunglasses',
price: 58,
details: "",
description: "",
image: "packages/shrine_images/1-0.jpg",
),
Product(
category: Category.accessories,
id: "2",
isFeatured: false,
name: 'Whitney belt',
price: 35,
details: "",
description: "",
image: "packages/shrine_images/2-0.jpg",
),
Product(
category: Category.accessories,
id: "3",
isFeatured: true,
name: 'Garden strand',
price: 98,
details: "",
description: "",
image: "packages/shrine_images/3-0.jpg",
),
Product(
category: Category.accessories,
id: "4",
isFeatured: false,
name: 'Strut earrings',
price: 34,
details: "",
description: "",
image: "packages/shrine_images/4-0.jpg",
),//removed other products to save space
];
if (category == Category.all) {
return allProducts;
} else {
return allProducts.where((Product p) {
return p.category == category;
}).toList();
}
}
//to get particular products by ID
Product findById(String id) {
var x = loadProducts(Category.all).firstWhere((prod) => prod.id == id);
print("findById successful");
print(x);
return x;
}
void addProduct() {
// _items.add(value);
notifyListeners();
}
}
Thank you everyone! Also, this problem stemmed from an earlier error in another post that a user was helping me to work through, but I didn't want to trouble them too much so I am reposting it here. Thank you #eamirho3einπŸ™πŸ™
Try this:
class ProductDetailsPage extends StatefulWidget {
static const routeName = '/user-products';
ProductDetailsPage({Key? key}) : super(key: key);
#override
State<ProductDetailsPage> createState() => _ProductDetailsPageState();
}
class _ProductDetailsPageState extends State<ProductDetailsPage> {
//const
DBHelper dbHelper = DBHelper();
List<Product> loadedProduct = []; // <----update this
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
//get particular productId using the ModalRoute class
final productId = ModalRoute.of(context)!.settings.arguments as String;
print(productId);
//use Provider package to find out ID by accessing method declared in Product()
setState(() {
loadedProduct = ProductsRepository().findById(productId);
});
});
}
#override
Widget build(BuildContext context) {
//List<bool> clicked = List.generate(10, (index) => false, growable: true);
final cart = Provider.of<CartProvider>(context);
void saveData(int index) {
dbHelper
.insert(
CartItem(
id: index,
title: loadedProduct.name,
price: loadedProduct.price.toDouble(),
quantity: ValueNotifier(1),
image: loadedProduct.image,
),
)
.then((value) {
cart.addTotalPrice(loadedProduct.price.toDouble());
cart.addCounter();
print('Product Added to cart');
}).onError((error, stackTrace) {
print(error.toString());
});
}
return Scaffold(
backgroundColor: Colors.orange[50],
appBar: AppBar(
backgroundColor: Colors.deepOrange[900],
title: const Text("Product details "),
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_outlined,
color: Colors.black,
semanticLabel: 'back to home',
),
onPressed: () {
Navigator.pop(context);
},
),
),
//body:
body: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 8.0),
shrinkWrap: true,
itemCount: loadedProduct.length,
itemBuilder: (context, index) {
return Card(
//SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(
height: 300,
width: double.infinity,
child: Image.asset(
loadedProduct.image,
fit: BoxFit.cover,
),
),
const SizedBox(height: 10),
Text(
'\$${loadedProduct.price}',
style: const TextStyle(
color: Colors.grey,
fontSize: 20,
),
),
const SizedBox(
height: 10,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.blueGrey.shade900),
onPressed: () {
saveData(Random().nextInt(1000));
},
child: const Text('Add to Cart')),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
width: double.infinity,
child: Text(
loadedProduct.description,
textAlign: TextAlign.center,
softWrap: true,
),
),
],
),
);
})
);
}
}
Hello I see some weird thing there.
ProductsRepository().findById(productId) return a single Product. Then
loadedProduct is a Product. Later on the ListView.builder on the itemCount parameter you used loadedProduct.length (loadedProduct in this point is used as a list of element). I dont know if the Product object have a .length method nor what returns that. But i think there is the problem. When you set itemCount: loadedProduct.length you are saying than the list will have x elements and later on the itemBuilder you are only using the data from loadedProduct to create the same widget in the list, x times.
Assuming that loadedProduct.length = 10, you will have a list view with the same Element 10 times
The next example use the loadedProduct to fill the products list. The product list will be used as the data on the ListView.builder. Then the list will show 3 time the same widget.
final products = [
loadedProduct,
loadedProduct,
loadedProduct
];
return Scaffold(
backgroundColor: Colors.orange[50],
appBar: AppBar(
backgroundColor: Colors.deepOrange[900],
title: const Text("Product details "),
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_outlined,
color: Colors.black,
semanticLabel: 'back to home',
),
onPressed: () {
Navigator.pop(context);
},
),
),
//body:
body: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 8.0),
shrinkWrap: true,
itemCount: products.length,
itemBuilder: (context, index) {
final targetProduct = products[index];
return Card(
//SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(
height: 300,
width: double.infinity,
child: Image.asset(
targetProduct.image,
fit: BoxFit.cover,
),
),
const SizedBox(height: 10),
Text(
'\$${targetProduct.price}',
style: const TextStyle(
color: Colors.grey,
fontSize: 20,
),
),
const SizedBox(
height: 10,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.blueGrey.shade900),
onPressed: () {
saveData(Random().nextInt(1000));
},
child: const Text('Add to Cart')),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
width: double.infinity,
child: Text(
targetProduct.description,
textAlign: TextAlign.center,
softWrap: true,
),
),
],
),
);
})
);
Just think what do you want to show on the list and use the right data. And review why the Product object have a .length method and what it return.
if you want to stop scroll listview add
NeverScrollableScrollPhysics
physics: const NeverScrollableScrollPhysics()
Inside ListView widget, use

how to Activate Elevatedbutton when checkboxListTile is selected

I'm making a page of terms and conditions.
I'm using checkboxListTile.
enter image description here
However, the Elevated Button shall be activated when selecting all items from the first to fifth items of the termsAndConditions.
The last sixth item may or may not be selected.
Otherwise, Elevated Button is disabled and the color should appear gray.
enter image description here
and Elevated Button must be activated when alltermsAndConditions are selected.
enter image description here
It’s too difficult.
Is there a solution?
I'd like to thank the people who answer.
class TermsAgreementPage extends StatelessWidget {
TermsAgreementPage({
Key? key,
}) : super(key: key);
CheckBoxState checkBoxState = Get.put(CheckBoxState(title: '', subTitle: ''));
final alltermsAndConditions = CheckBoxState(title: 'μ „μ²΄λ™μ˜', subTitle: '');
final termsAndConditions = [
CheckBoxState(
title: '이용 μ•½κ΄€ λ™μ˜(ν•„μˆ˜)',
subTitle: 'λ‹ˆμ–΄μ—‘μŠ€ μ„œλΉ„μŠ€ 이용 톡합 μ•½κ΄€μž…λ‹ˆλ‹€.'),
CheckBoxState(
title: 'κ°œμΈμ •λ³΄ 처리방침 λ™μ˜(ν•„μˆ˜)',
subTitle: 'κ°œμΈμ •λ³΄λ³΄ν˜Έ 포털 법λ₯ μ— μ˜κ±°ν•œ μ œκ³΅λ™μ˜λ‘œ ν•„μˆ˜ μ‚¬ν•­μž…λ‹ˆλ‹€.'),
CheckBoxState(
title: 'κ°œμΈμ •λ³΄ 제3자 μ œκ³΅λ™μ˜(ν•„μˆ˜)',
subTitle: 'κ°œμΈμ •λ³΄λ³΄ν˜Έ 포털 법λ₯ μ— μ˜κ±°ν•œ 제곡 λ™μ˜λ‘œ ν•„μˆ˜ μ‚¬ν•­μž…λ‹ˆλ‹€.'),
CheckBoxState(
title: 'μœ„μΉ˜κΈ°λ°˜ μ„œλΉ„μŠ€ μ΄μš©μ•½κ΄€(ν•„μˆ˜)',
subTitle: 'μ£Όλ³€ κ°€κ²Œλ“€ 검색에 μ‚¬μš©λ©λ‹ˆλ‹€.'),
CheckBoxState(
title: 'μ „μžκΈˆμœ΅κ±°λž˜ μ΄μš©μ•½κ΄€(ν•„μˆ˜)',
subTitle: 'ꡬ맀 λ˜λŠ” 결제 사항이 μžˆμ„ 경우 제곡 λ™μ˜λ‘œ ν•„μˆ˜ μ‚¬ν•­μž…λ‹ˆλ‹€.'),
CheckBoxState(
title: 'λ‹ˆμ–΄μ—‘μŠ€ ν˜œνƒ μ•Œλ¦Ό λ™μ˜(선택)',
subTitle: '미선택 μ‹œ μ£Όλ³€κ°€κ²Œ 할인 및 만기 λ‹€κ°€μ˜€λŠ” 쿠폰 μ•Œλ¦Ό μ‚¬μš© λΆˆκ°€.'),
];
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 80.0, bottom: 40),
child: Column(
children: [
Expanded(
flex: 1,
child: Container(
width: size.width,
child: Column(
children: const [
Text(
'이용 μ•½κ΄€ λ™μ˜',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
'μ•„λž˜μ˜ 약관에 λ™μ˜ ν•˜μ‹  ν›„ μ„œλΉ„μŠ€λ₯Ό μ΄μš©ν•΄ μ£Όμ‹œκΈ° λ°”λžλ‹ˆλ‹€.',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey),
),
],
),
),
),
Expanded(
flex: 6,
child: Obx(
() => ListView(
children: [
buildGroupCheckbox(
CheckBoxState(title: 'μ „μ²΄λ™μ˜', subTitle: '')),
const Divider(color: Colors.grey, height: 2),
...termsAndConditions.map(buildCheckbox).toList()
],
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: 50, vertical: 10),
textStyle: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w400,
color: Colors.white)),
onPressed: () {
},
child: const Text('μ‹œμž‘ν•˜κΈ°'),
),
],
),
),
);
}
Widget buildGroupCheckbox(CheckBoxState checkBoxState) {
return CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
secondary: TextButton(
onPressed: () {
Get.toNamed('/home');
},
child: const Text(
'전문보기',
style: TextStyle(fontSize: 10.0, color: Colors.blue),
)),
title: Text(
checkBoxState.title,
style: const TextStyle(fontSize: 12),
),
subtitle: Text(
checkBoxState.subTitle,
style: const TextStyle(fontSize: 9, color: Colors.grey),
),
onChanged: toggleCheckBox,
value: alltermsAndConditions.isChecked.value,
);
}
void toggleCheckBox(bool? value) {
if (value == null) return;
alltermsAndConditions.isChecked.value = value;
for (var termsAndConditions in termsAndConditions) {
termsAndConditions.isChecked.value = value;
}
}
Widget buildCheckbox(CheckBoxState checkBoxState) {
return CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
// μ™Όμͺ½μ— λ„€λͺ¨ λ°•μŠ€ μœ„μΉ˜
title: Text(
checkBoxState.title,
style: const TextStyle(fontSize: 12),
),
subtitle: Text(
checkBoxState.subTitle,
style: const TextStyle(fontSize: 9, color: Colors.grey),
),
onChanged: (value) {
checkBoxState.isChecked.value = value!;
alltermsAndConditions.isChecked.value = termsAndConditions.every(
(termsAndConditions) => termsAndConditions.isChecked.value);
},
value: checkBoxState
.isChecked.value
);
}
}
********** controller **********
class CheckBoxState extends GetxController {
RxBool isChecked = false.obs;
final String title; // CheckBoxListTile 의 타이틀 제λͺ©
final String subTitle; // CheckBoxListTile 의 μ„œλΈŒνƒ€μ΄ν‹€ λ‚΄μš©
CheckBoxState({
required this.title,
required this.subTitle,
});
}
enter code here
First of all you should change this line :
termsAndConditions.map(buildCheckbox).toList()
to this :
termsAndConditions.asMap().forEach((key, value) {
buildCheckbox(value,key);
});
//or if this does work you add .toList()
termsAndConditions.asMap().forEach((key, value) {
buildCheckbox(value,key);
}).toList();
then you change the buildCheckbox function to include the key/index of the list you are passing
Widget buildGroupCheckbox(CheckBoxState checkBoxState,int index) {
...
}
then you need to add an array of booleans to keep track of the checkboxes
final checkBoxState= [false,false,false,false,false,false];
now you are implementing something like this :
onChanged: (value) {
//new code
if (value){checkBoxState[index]=true;}
else {checkBoxState[index]=false;}
checkBoxState.isChecked.value = value!;
alltermsAndConditions.isChecked.value = termsAndConditions.every(
(termsAndConditions) => termsAndConditions.isChecked.value);
},
Finally you can each individual checkbox statue and can do anything you want :
onPressed: (!sumof5first())?null:() {
//does something
},
}
bool sumof5first(){
int sum = 0;
for (int i=0;i<5;i++){
if (checkBoxState[i]==true){sum++}
}
if (sum>4){return true}
else {return false}
}

How to Remove cart item using provider

I am using provider class in which i can add items to the cart list using product id but when i use the similar function to remove the item from cart it seems the function dont find the product id.or im doing somting wrong.
The function i used to add items in provider class:
//---------------------------------------add item
void addItem({
required CartItem product,
}) {
if (items.containsKey(product.id)) {
final double value = product.price;
items.update(
product.id,
(cartitem) => cartitem.copy(
qnt: cartitem.qnt + 1,
price: cartitem.price + value,
),
);
} else {
items.putIfAbsent(
product.id,
() => product.copy(
id: DateTime.now().toString(),
qnt: 1,
));
}
//---------------------------------------------------------total price
_totalPrice = _totalPrice + product.price;
notifyListeners();
}
and the list in my cart:
//----------- items cart list
Widget buildCarditems(BuildContext context) {
final porvidr = Provider.of<ShopProvider>(context, listen: false);
if (porvidr.items.isEmpty) {
return Center(
child: Text(
'Cart is Empty',
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
);
} else {
return ListView(
children: porvidr.items.values.map(buildcarditem).toList(),
);
}
}
//------------cart listtile
Widget buildcarditem(CartItem c_item) {
return ListTile(
leading: CircleAvatar(backgroundImage: AssetImage(c_item.imgUrl)),
title: Row(
children: [
Text(
'${c_item.qnt}x',
style: TextStyle(color: Colors.white),
),
SizedBox(
width: 10,
),
Expanded(
child: Text(
c_item.title,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
trailing: Text(
'\$${c_item.price}',
style: TextStyle(
color: Colors.white,
),
));
}
where can i use the ontap function to get the product id?
You can add it anywhere you want on each list item, you can wrap the list tile in n gesture detector widget. it depends on the requirement.

How to delete an item from cart?

recently I have followed a Youtube tutorial where it shows how to add product item to cart. But in the video, it didn't show how to delete an item from cart. Video link: https://www.youtube.com/watch?v=K8d3qqbP3qk
ProductModel.dart
class ProductModel{
String name;
int price;
String image;
ProductModel(String name, int price, String image){
this.name = name;
this.price = price;
this.image = image;
}
}
ProductScreen.dart
import 'package:flutter/material.dart';
import '../ProductModel.dart';
import 'package:ecommerce_int2/models/product.dart';
class ProductScreen2 extends StatelessWidget {
final ValueSetter<ProductModel> _valueSetter;
ProductScreen2(this._valueSetter);
List<ProductModel> products = [
ProductModel("Grey Jacket", 100, 'assets/jacket_1.png'),
ProductModel("Brown Pants", 60, 'assets/jeans_9.png'),
ProductModel("Grey Pants", 50, 'assets/jeans_6.png'),
ProductModel("Orange Pants", 70, 'assets/jeans_8.png'),
ProductModel("Long Jeans", 80, 'assets/jeans_2.png'),
ProductModel("Black and Blue Cap", 40, 'assets/cap_2.png'),
ProductModel("Black Cap", 30, 'assets/cap_6.png'),
ProductModel("Red Cap", 35, 'assets/cap_4.png'),
];
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.separated(
itemBuilder: (context, index){
return ListTile(
leading: Image.asset(
products[index].image,
width: 100,
height: 100,
fit: BoxFit.fitWidth,
),
title: Text(products[index].name),
trailing: Text("\RM${products[index].price}", style: TextStyle(color: Colors.redAccent, fontSize: 20, fontWeight: FontWeight.w500),),
onTap: (){
_valueSetter(products[index]);
},
);
},
separatorBuilder: (context, index){
return Divider();
},
itemCount: products.length
),
);
}
}
CheckoutScreen.dart
import 'package:flutter/material.dart';
import 'package:ecommerce_int2/screens/address/add_address_page.dart';
import 'package:device_apps/device_apps.dart';
class CheckoutScreen extends StatelessWidget {
final cart;
final sum;
CheckoutScreen(this.cart, this.sum);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListView.separated(
itemBuilder: (context, index){
return ListTile(
title: Text(cart[index].name),
trailing: Text("\RM${cart[index].price}", style: TextStyle(color: Colors.redAccent, fontSize: 20, fontWeight: FontWeight.w500),),
onTap: (){
},
);
},
separatorBuilder: (context, index){
return Divider();
},
itemCount: cart.length,
shrinkWrap: true,
),
Divider(),
Text("Total : \RM$sum", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500), textAlign: TextAlign.right,),
SizedBox(
height: 20,
),
Text("Remarks", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),),
TextFormField(
decoration: InputDecoration(
hintText: ('Example: Red Cap: Free Size, Grey Jacket: UK, M Size'),
),
maxLines: 5,
),
SizedBox(
height: 50,
),
RaisedButton(
color: Theme.of(context).accentColor,
child: Text('Buy Now',style: TextStyle(color: Colors.white, fontSize: 20)),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => AddAddressPage()));
},
),
RaisedButton(
color: Theme.of(context).accentColor,
child: Text('Try Out',style: TextStyle(color: Colors.white, fontSize: 20)),
onPressed: () => DeviceApps.openApp('com.DefaultCompany.clothestryingfunction2'),
),
],
),
);
}
}
add_to_cart.dart
import 'package:ecommerce_int2/ProductModel.dart';
import 'package:ecommerce_int2/screens/CheckoutScreen.dart';
import 'package:ecommerce_int2/screens/ProductScreen.dart';
import 'package:flutter/material.dart';
class CartApp extends StatefulWidget {
#override
_CartAppState createState() => _CartAppState();
}
class _CartAppState extends State<CartApp> {
List<ProductModel> cart = [];
int sum = 0;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("Add To Cart"),
bottom: TabBar(
tabs: <Widget>[
Tab(text: "Products",),
Tab(text: "Cart",),
],
),
),
body: TabBarView(
children: <Widget>[
ProductScreen2((selectedProduct){
setState(() {
cart.add(selectedProduct);//update
sum = 0;
cart.forEach((item){
sum = sum + item.price;
});
});
}),
CheckoutScreen(cart, sum),
],
),
),
);
}
}
The goal is to remove the selected item from cart and minus the selected item's price from the sum. Can anyone tell me how to do that?
First, you need to add a callback in the CheckoutScreen:
class CheckoutScreen extends StatelessWidget {
final cart;
final sum;
final ValueSetter<ProductModel> _valueDeleter;
CheckoutScreen(this.cart, this.sum, this._valueDeleter);
...
After that, add the callback function when using it in the TabBarView in CartApp:
CheckoutScreen(cart, sum, (deleteProduct) {
setState(() {
// Use this loop instead of cart.removeWhere() to delete 1 item at a time
for (var i = 0; i < cart.length; i++) {
if (cart[i].name == deleteProduct.name) {
cart.removeAt(i);
break;
}
}
sum = 0;
cart.forEach((item) {
sum = sum + item.price;
});
});
}),
Finally, add a button in the ListTile within CheckoutScreen to initiate the delete action (I'm using a Row in title here for simplicity):
ListTile(
...
title: Row(
children: [
IconButton(
icon: Icon(Icons.delete),
color: Colors.red,
onPressed: () => _valueDeleter(cart[index]),
),
Text(cart[index].name),
],
),
trailing: Text(
...