feat: photo_detail tag

release/mobdr-v0.0.1
Frédérik Benoist 2023-04-01 21:02:43 +02:00
parent ed37fed45d
commit 2562328146
5 changed files with 183 additions and 492 deletions

View File

@ -165,4 +165,10 @@ class GlobalStyle {
static const Icon iconTime = static const Icon iconTime =
Icon(Icons.access_time, size: 14, color: SOFT_GREY); Icon(Icons.access_time, size: 14, color: SOFT_GREY);
static const TextStyle detailFoodOptions =
TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold);
static const TextStyle detailFoodSubOptions =
TextStyle(color: BLACK77, fontSize: 12);
} }

View File

@ -67,7 +67,7 @@ class ObjectBox {
etabBox.removeAll(); etabBox.removeAll();
concurrentBox.removeAll(); concurrentBox.removeAll();
visiteBox.removeAll(); visiteBox.removeAll();
visiteTagBox.removeAll(); //visiteTagBox.removeAll();
//photoBox.removeAll(); //photoBox.removeAll();
//photoTypologyBox.removeAll(); //photoTypologyBox.removeAll();
@ -336,6 +336,15 @@ class ObjectBox {
store.box<VisiteTag>().put(_VisiteTag); store.box<VisiteTag>().put(_VisiteTag);
} }
List<String> getVisiteTagsLabels() {
final query = visiteTagBox
.query(VisiteTag_.langage.equals('fr'))
.order(VisiteTag_.libelle)
.build();
final visiteTags = query.find();
return visiteTags.map((VisiteTag) => VisiteTag.libelle).toList();
}
int getVisiteTagCount() { int getVisiteTagCount() {
return visiteTagBox.count(); return visiteTagBox.count();
} }
@ -437,6 +446,14 @@ class ObjectBox {
store.box<PhotoTypology>().put(_PhotoTypology); store.box<PhotoTypology>().put(_PhotoTypology);
} }
List<String> getPhotoTypologiesLabels() {
final query = photoTypologyBox.query().order(PhotoTypology_.ordre).build();
final photoTypologies = query.find();
return photoTypologies
.map((photoTypology) => photoTypology.libelle)
.toList();
}
Stream<List<PhotoTypology>> getPhotoTypologies() { Stream<List<PhotoTypology>> getPhotoTypologies() {
// Query for all Typologies, sorted by their order. // Query for all Typologies, sorted by their order.
// https://docs.objectbox.io/queries // https://docs.objectbox.io/queries

View File

@ -1,23 +1,19 @@
import 'dart:io'; import 'dart:io';
import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:filter_list/filter_list.dart';
import 'package:mobdr/config/constant.dart'; import 'package:mobdr/config/constant.dart';
import 'package:mobdr/config/global_style.dart'; import 'package:mobdr/config/global_style.dart';
import 'package:mobdr/model/related_product_model.dart'; import 'package:mobdr/main.dart';
import 'package:mobdr/model/review_model.dart';
import 'package:mobdr/ui/general/chat_us.dart'; import 'package:mobdr/ui/general/chat_us.dart';
import 'package:mobdr/ui/general/notification.dart'; import 'package:mobdr/ui/general/notification.dart';
import 'package:mobdr/ui/general/product_detail/delivery_estimated.dart';
import 'package:mobdr/ui/general/product_detail/product_description.dart';
import 'package:mobdr/ui/general/product_detail/product_review.dart';
import 'package:mobdr/ui/home/product_category.dart'; import 'package:mobdr/ui/home/product_category.dart';
import 'package:mobdr/ui/home/search.dart'; import 'package:mobdr/ui/home/search.dart';
import 'package:mobdr/ui/reusable/reusable_widget.dart'; import 'package:mobdr/ui/reusable/reusable_widget.dart';
import 'package:mobdr/ui/shopping_cart/tab_shopping_cart.dart'; import 'package:mobdr/ui/shopping_cart/tab_shopping_cart.dart';
import 'package:mobdr/ui/reusable/cache_image_network.dart';
import 'package:mobdr/ui/reusable/global_function.dart'; import 'package:mobdr/ui/reusable/global_function.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class PhotoDetailPage extends StatefulWidget { class PhotoDetailPage extends StatefulWidget {
final String name; final String name;
@ -50,40 +46,22 @@ class _PhotoDetailPageState extends State<PhotoDetailPage> {
final _globalFunction = GlobalFunction(); final _globalFunction = GlobalFunction();
final _reusableWidget = ReusableWidget(); final _reusableWidget = ReusableWidget();
final List<String> _imgProductSlider = []; // Typology list
int _currentImageSlider = 0; List<String> _typologyList = objectbox.getPhotoTypologiesLabels();
int _typologyIndex = 0;
// size data List<String> _chickenParts = [];
List<String> _sizeList = ['XS', 'S', 'M', 'L', 'XL', 'XXL']; int _maxChickenParts = 2;
int _sizeIndex = 0;
// color data
List<String> _colorList = [
'Red',
'Black',
'Green',
'White',
'Blue',
'Yellow',
'Pink'
];
int _colorIndex = 0;
// wishlist
bool _isLove = false;
// shopping cart count // shopping cart count
int _shoppingCartCount = 3; int _shoppingCartCount = 3;
// tag(s) list
final List<String> tagsList = objectbox.getVisiteTagsLabels();
List<String> selectedTagsList = [];
@override @override
void initState() { void initState() {
// image slider for the product
_imgProductSlider.add(widget.image);
_imgProductSlider.add(widget.image);
_imgProductSlider.add(widget.image);
_imgProductSlider.add(widget.image);
_imgProductSlider.add(widget.image);
super.initState(); super.initState();
} }
@ -94,7 +72,6 @@ class _PhotoDetailPageState extends State<PhotoDetailPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double boxImageSize = (MediaQuery.of(context).size.width / 3);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
iconTheme: IconThemeData( iconTheme: IconThemeData(
@ -171,13 +148,9 @@ class _PhotoDetailPageState extends State<PhotoDetailPage> {
children: [ children: [
Image.file(File(widget.image), fit: BoxFit.cover), Image.file(File(widget.image), fit: BoxFit.cover),
//_createProductSlider(), //_createProductSlider(),
_createProductPriceTitleEtc(), _buildPhotoTypology(),
_createProductVariant(), _buildPhotoVisibility(),
_createDeliveryEstimated(), _buildPhotoTag(),
_createProductInformation(),
_createProductDescription(),
_createProductRelated(boxImageSize),
_createProductReview(),
SizedBox(height: 16) SizedBox(height: 16)
], ],
), ),
@ -283,108 +256,7 @@ class _PhotoDetailPageState extends State<PhotoDetailPage> {
); );
} }
Widget _createProductSlider() { Widget _buildPhotoTypology() {
return Stack(
children: [
CarouselSlider(
items: _imgProductSlider
.map((item) => Container(
child:
buildCacheNetworkImage(width: 0, height: 0, url: item),
))
.toList(),
options: CarouselOptions(
aspectRatio: 1,
viewportFraction: 1.0,
autoPlay: false,
enlargeCenterPage: false,
onPageChanged: (index, reason) {
setState(() {
_currentImageSlider = index;
});
}),
),
Positioned(
bottom: 16,
left: 16,
child: Container(
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
decoration: BoxDecoration(
color: SOFT_BLUE, borderRadius: BorderRadius.circular(4)),
child: Text(
(_currentImageSlider + 1).toString() +
'/' +
_imgProductSlider.length.toString(),
style: TextStyle(color: Colors.white, fontSize: 11)),
),
),
],
);
}
Widget _createProductPriceTitleEtc() {
return Container(
color: Colors.white,
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(widget.photo.toString() + ' photo(s)',
style: GlobalStyle.detailProductPrice),
GestureDetector(
onTap: () {
setState(() {
if (_isLove == true) {
_isLove = false;
Fluttertoast.showToast(
msg: "La photo n'est plus taguée comme favorite",
toastLength: Toast.LENGTH_LONG);
} else {
Fluttertoast.showToast(
msg: "La photo est taguée comme favorite",
toastLength: Toast.LENGTH_LONG);
_isLove = true;
}
});
},
child: Icon(Icons.favorite,
color: _isLove == true ? ASSENT_COLOR : BLACK_GREY,
size: 28),
)
],
),
SizedBox(height: 12),
Text(widget.name,
style: TextStyle(
fontSize: 14,
)),
SizedBox(height: 12),
IntrinsicHeight(
child: Row(
children: [
Text(widget.date,
style: TextStyle(fontSize: 13, color: BLACK_GREY)),
VerticalDivider(
width: 30,
thickness: 1,
color: Colors.grey[300],
),
Icon(Icons.location_on, color: SOFT_GREY, size: 16),
Text('Brooklyn',
style: TextStyle(fontSize: 13, color: SOFT_GREY))
],
),
),
],
),
);
}
Widget _createProductVariant() {
return Container( return Container(
margin: EdgeInsets.only(top: 12), margin: EdgeInsets.only(top: 12),
padding: EdgeInsets.all(16), padding: EdgeInsets.all(16),
@ -392,37 +264,13 @@ class _PhotoDetailPageState extends State<PhotoDetailPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Variant', style: GlobalStyle.sectionTitle), Text('Typologie', style: GlobalStyle.sectionTitle),
SizedBox( SizedBox(
height: 16, height: 16,
), ),
Row(
children: [
Text('Size : ',
style: TextStyle(color: BLACK_GREY, fontSize: 14)),
Text(_sizeList[_sizeIndex],
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
Wrap( Wrap(
children: List.generate(_sizeList.length, (index) { children: List.generate(_typologyList.length, (index) {
return radioSize(_sizeList[index], index); return radioSize(_typologyList[index], index);
}),
),
SizedBox(
height: 16,
),
Row(
children: [
Text('Color : ',
style: TextStyle(color: BLACK_GREY, fontSize: 14)),
Text(_colorList[_colorIndex],
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
Wrap(
children: List.generate(_colorList.length, (index) {
return radioColor(_colorList[index], index);
}), }),
), ),
], ],
@ -433,101 +281,105 @@ class _PhotoDetailPageState extends State<PhotoDetailPage> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
_sizeIndex = index; _typologyIndex = index;
}); });
}, },
child: Container( child: Container(
padding: EdgeInsets.fromLTRB(12, 8, 12, 8), padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
margin: EdgeInsets.only(right: 8, top: 8), margin: EdgeInsets.only(right: 8, top: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _sizeIndex == index ? SOFT_BLUE : Colors.white, color: _typologyIndex == index ? SOFT_BLUE : Colors.white,
border: Border.all( border: Border.all(
width: 1, width: 1,
color: _sizeIndex == index ? SOFT_BLUE : Colors.grey[300]!), color: _typologyIndex == index ? SOFT_BLUE : Colors.grey[300]!),
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(10) // <--- border radius here Radius.circular(10) // <--- border radius here
)), )),
child: Text(txt, child: Text(txt,
style: TextStyle( style: TextStyle(
color: _sizeIndex == index ? Colors.white : CHARCOAL)), color: _typologyIndex == index ? Colors.white : CHARCOAL)),
), ),
); );
} }
Widget radioColor(String txt, int index) { Widget _checboxChicken({value = 'breast', primaryText = 'Chicken Breast'}) {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () { onTap: () {
setState(() { setState(() {
_colorIndex = index; if (_chickenParts.contains(value)) {
_chickenParts.remove(value);
} else {
if (_chickenParts.length < _maxChickenParts) {
_chickenParts.add(value);
}
}
}); });
}, },
child: Container( child: Row(
padding: EdgeInsets.fromLTRB(12, 8, 12, 8), children: [
margin: EdgeInsets.only(right: 8, top: 8), Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: _colorIndex == index ? SOFT_BLUE : Colors.white, border: Border.all(
border: Border.all( width: 1,
width: 1, color: (_chickenParts.contains(value))
color: _colorIndex == index ? SOFT_BLUE : Colors.grey[300]!), ? PRIMARY_COLOR
borderRadius: BorderRadius.all( : BLACK77),
Radius.circular(10) // <--- border radius here borderRadius: BorderRadius.all(Radius.circular(4.0)),
)), ),
child: Text(txt, child: Padding(
style: TextStyle( padding: const EdgeInsets.all(2),
color: _colorIndex == index ? Colors.white : CHARCOAL)), child: (_chickenParts.contains(value))
? Icon(
Icons.check,
size: 12.0,
color: PRIMARY_COLOR,
)
: Icon(
Icons.check_box_outline_blank,
size: 12.0,
color: Colors.white,
),
),
),
SizedBox(width: 16),
Text(primaryText,
style: TextStyle(
fontSize: 13,
color: BLACK77,
fontWeight: (_chickenParts.contains(value))
? FontWeight.bold
: FontWeight.normal)),
],
), ),
); );
} }
Widget _createDeliveryEstimated() { Widget _buildPhotoVisibility() {
return GestureDetector( return Container(
onTap: () { margin: EdgeInsets.only(top: 12),
Navigator.push(context, padding: EdgeInsets.all(16),
MaterialPageRoute(builder: (context) => DeliveryEstimatedPage())); color: Colors.white,
}, child: Column(
child: Container( children: [
margin: EdgeInsets.only(top: 12), Row(
padding: EdgeInsets.all(16),
color: Colors.white,
child: Row(
children: [ children: [
Flexible( Text('Visibility', style: GlobalStyle.sectionTitle),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Delivery', style: GlobalStyle.sectionTitle),
SizedBox(
height: 16,
),
RichText(
text: new TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: new TextStyle(
fontSize: 15.5,
color: BLACK_GREY,
),
children: <TextSpan>[
new TextSpan(
text:
'Calculate the estimated cost for shipping goods to '),
new TextSpan(
text: 'West New York, NJ',
style:
new TextStyle(fontWeight: FontWeight.bold)),
],
),
),
],
),
),
Icon(Icons.chevron_right, size: 36, color: CHARCOAL)
], ],
)), ),
SizedBox(height: 16),
_checboxChicken(value: 'public', primaryText: 'Public'),
Divider(
height: 32,
color: Colors.grey[400],
),
_checboxChicken(value: 'principal', primaryText: 'Principal')
],
),
); );
} }
Widget _createProductInformation() { Widget _buildPhotoTag() {
return Container( return Container(
margin: EdgeInsets.only(top: 12), margin: EdgeInsets.only(top: 12),
padding: EdgeInsets.all(16), padding: EdgeInsets.all(16),
@ -535,261 +387,66 @@ class _PhotoDetailPageState extends State<PhotoDetailPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Information', style: GlobalStyle.sectionTitle), Text("Tags sélectionnés:"),
SizedBox( SizedBox(height: 8),
height: 16, selectedTagsList.isEmpty
), ? Text("Aucun tag sélectionné.")
Row( : Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Weight', style: TextStyle(color: BLACK_GREY)),
Text('300 Gram', style: TextStyle(color: BLACK_GREY))
],
),
SizedBox(
height: 12,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Condition', style: TextStyle(color: BLACK_GREY)),
Text('Second', style: TextStyle(color: BLACK_GREY))
],
),
SizedBox(
height: 12,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Category', style: TextStyle(color: BLACK_GREY)),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductCategoryPage(
categoryId: 3, categoryName: 'Electronic')));
},
child: Text('Electronic', style: TextStyle(color: SOFT_BLUE)),
)
],
),
],
));
}
Widget _createProductDescription() {
return Container(
margin: EdgeInsets.only(top: 12),
padding: EdgeInsets.all(16),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Description',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(
height: 16,
),
Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nQuisque tortor tortor, ultrices id scelerisque a, elementum id elit. Maecenas feugiat tellus sed augue malesuada, id tempus ex sodales.'),
SizedBox(
height: 16,
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDescriptionPage(
name: widget.name, image: widget.image)));
},
child: Center(
child: Text('Read More', style: TextStyle(color: SOFT_BLUE)),
),
),
],
));
}
Widget _createProductRelated(boxImageSize) {
return Container(
margin: EdgeInsets.only(top: 12),
padding: EdgeInsets.only(bottom: 16),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.all(16),
child: Text('Related Product', style: GlobalStyle.sectionTitle),
),
Container(
height: boxImageSize *
GlobalStyle.horizontalProductHeightMultiplication,
child: ListView.builder(
padding: EdgeInsets.only(left: 12, right: 12),
scrollDirection: Axis.horizontal,
itemCount: relatedProductData.length,
itemBuilder: (BuildContext context, int index) {
return Container(
width: boxImageSize + 10,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 2,
color: Colors.white,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoDetailPage(
name: relatedProductData[index].name,
image: relatedProductData[index].image,
price: relatedProductData[index].price,
rating: relatedProductData[index].rating,
review: relatedProductData[index].review,
sale: 36)));
},
child: Column(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
child: buildCacheNetworkImage(
width: boxImageSize + 10,
height: boxImageSize + 10,
url: relatedProductData[index].image)),
Container(
margin: EdgeInsets.fromLTRB(8, 8, 8, 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
relatedProductData[index].name,
style: GlobalStyle.productName,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Container(
margin: EdgeInsets.only(top: 5),
child: Text(
'\$ ' +
_globalFunction
.removeDecimalZeroFormat(
relatedProductData[index]
.price),
style: GlobalStyle.productPrice),
),
Container(
margin: EdgeInsets.only(top: 5),
child: Row(
children: [
_reusableWidget.createRatingBar(
rating: relatedProductData[index]
.rating,
size: 12),
Text(
'(' +
relatedProductData[index]
.review
.toString() +
')',
style:
GlobalStyle.productTotalReview)
],
),
)
],
),
),
],
),
),
),
);
},
),
),
],
));
}
Widget _createProductReview() {
return Container(
margin: EdgeInsets.only(top: 12),
padding: EdgeInsets.all(16),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Review', style: GlobalStyle.sectionTitle),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductReviewPage()));
},
child: Text('View All',
style: GlobalStyle.viewAll.copyWith(color: SOFT_BLUE),
textAlign: TextAlign.end),
)
],
),
SizedBox(
height: 8,
),
Row(
children: [
_reusableWidget.createRatingBar(
rating: widget.rating, size: 12),
Text('(' + widget.review.toString() + ')',
style: TextStyle(fontSize: 11, color: SOFT_GREY))
],
),
Column(
children: List.generate(reviewData.length, (index) {
return Column(
children: [
Divider(
height: 32,
color: Colors.grey[400],
),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: tagsList.map((tag) => Text("- $tag")).toList(),
Text(reviewData[index].date, ),
style: TextStyle(fontSize: 13, color: SOFT_GREY)), SizedBox(height: 16),
SizedBox(height: 4), ElevatedButton(
Row( onPressed: () => _openTagSelectionDialog(),
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Text("Modifier"),
children: [ ),
Text(reviewData[index].name,
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.bold)),
_reusableWidget.createRatingBar(
rating: reviewData[index].rating, size: 12),
],
),
SizedBox(height: 4),
Text(reviewData[index].review)
],
))
],
);
})),
], ],
)); ));
} }
Future<void> _openTagSelectionDialog() async {
await FilterListDialog.display<String>(
context,
hideSelectedTextCount: true,
themeData: FilterListThemeData(context),
headlineText: 'Select Users',
height: 500,
listData: tagsList,
selectedListData: selectedTagsList,
choiceChipLabel: (item) => item,
validateSelectedItem: (list, val) => list!.contains(val),
controlButtons: [ControlButtonType.All, ControlButtonType.Reset],
onItemSearch: (user, query) {
/// When search query change in search bar then this method will be called
///
/// Check if items contains query
return user.toLowerCase().contains(query.toLowerCase());
},
onApplyButtonClick: (list) {
setState(() {
selectedTagsList = list!;
});
Navigator.pop(context);
},
/// uncomment below code to create custom choice chip
/*
choiceChipBuilder: (context, item, isSelected) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(
border: Border.all(
color: isSelected! ? Colors.blue[300]! : Colors.grey[300]!,
)),
child: Text(
item,
style: TextStyle(
color: isSelected ? Colors.blue[300] : Colors.grey[500]),
),
);
},*/
);
}
} }

View File

@ -321,6 +321,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.4" version: "6.1.4"
filter_list:
dependency: "direct main"
description:
name: filter_list
sha256: "2d80d6d19beb7847c1176e8bf6fe06d302b23eb7d1bf48c231dd730409ff9b4d"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:

View File

@ -55,9 +55,12 @@ dependencies:
permission_handler: 10.2.0 permission_handler: 10.2.0
image: ^4.0.15 image: ^4.0.15
# https://pub.dev/packages/filter_list/install
filter_list: ^1.0.2
flutter_cache_manager: ^3.3.0 flutter_cache_manager: ^3.3.0
#https://pub.dev/packages/intl # https://pub.dev/packages/intl
intl: 0.17.0 intl: 0.17.0
carousel_slider: 4.2.1 carousel_slider: 4.2.1