Mohammed Abu Yousef نشر 25 يوليو 2021 أرسل تقرير نشر 25 يوليو 2021 حاولت كثيرا لكن للأسف لم تعمل، عندي بالصفحة الرئيسية تظهر ال categories أريد عند الضغط على أي category تظهر ال products الخاصة فيه تحت في نفس الشاشة ( يعني ما في navigation ) حسب الصورة المرفقة ملاحظة / ال data جاية من API 1 اقتباس
1 بلال زيادة نشر 25 يوليو 2021 أرسل تقرير نشر 25 يوليو 2021 هناك طريقة وهي استخدام StreamController بحيث هي طريقة من طرق إدارة الحالة في فلاتر. و الأمر معقد جداً و يجب أن تكون على دراية جيدة بكيفية استخدام StreamController. لاحظ بأنه يجب أن تقوم بتعلم StreamController حتى تفهم الإجابة جيداً لأنه في الوهلة الأولى ستجد أن الأمر قد يكون فيه صعوبة يمكنك إنشاء ملف وليكن مسؤول عن التنصت على أي حدث جديد و يعكسه على الواجهة أي يقوم بتنفيذ هذا الحدث على الواجهة و الميزة في ذلك إنها تقوم بتنفيذ الحدث بدون اللجوء إلى بناء نفسها من الصفر و هذا الملف فليكن اسمه StreamProductsBloc.dart ويمكننا تعريف عدة متغيرات في هذا الملف بهذا الشكل List<ProductsModel>? products; final StreamController<List<ProductsModel>> _productsController = StreamController<List<ProductsModel>>.broadcast(); final StreamController<int> _categoryController = StreamController<int>.broadcast(); Stream<List<ProductsModel>> get productsStream => _productsController.stream; StreamSink<int> get fetchProducts => _categoryController.sink; Stream<int> get getProductsForCategoriescategory => _categoryController.stream; APIService? apiService; int categoryID = 0; List<ProductsModel>? products; بحيث أول شيء نقوم بتعريف الكلاس مودل الخاص بالمنتجات. final StreamController<List<ProductsModel>> _productsController = StreamController<List<ProductsModel>>.broadcast(); و من ثم نقوم بتعريف StreamController ليقوم بالتحكم في العملية , ويتم توفير واجهات مختلفة لإنشاء تدفقات أحداث متنوعة. final StreamController<int> _categoryController = StreamController<int>.broadcast(); وحدة تحكم حيث يمكن الاستماع إليه أكثر من مرة . Stream<List<ProductsModel>> get productsStream => _productsController.stream; ثم نقوم بالحصول على البث للمراقبة. StreamSink<int> get fetchProducts => _categoryController.sink; ثم نقوم بإدخال حدث إضافي عن طريق StreamSink. StreamProductsBloc() { //this.categoryID = categoryID; this.products = []; apiService = APIService(); _productsController.add(products!); _categoryController.add(this.categoryID); _categoryController.stream.listen(_fetchCategoriesFromApi); } Future<void> _fetchCategoriesFromApi(int category) async { this.products = await apiService!.getProductsForCategories(category); _productsController.add(this.products!); } @override void dispose(){ _productsController.close(); _categoryController.close(); } نقوم بإنشاء دالة جلب بيانات التصنيف عند تمرير id التصنيف و نقوم بإرجاع جميع المنتجات و من ثم نقوم بإضافة هذه البيانات إلى وحدة التحكم. Future<void> _fetchCategoriesFromApi(int category) async { this.products = await apiService!.getProductsForCategories(category); _productsController.add(this.products!); } ثم نقوم بإضافة المنتجات و id التصنيف إلى وحدة التحكم في دالة StreamProductsBloc ثم نقوم بمراقبة الحدث عن طريق استخدام listen StreamProductsBloc() { //this.categoryID = categoryID; this.products = []; apiService = APIService(); _productsController.add(products!); _categoryController.add(this.categoryID); _categoryController.stream.listen(_fetchCategoriesFromApi); } ثم نقوم بإنشاء ملف واجهة عرض البيانات وليكن مثلاً باسم products_screen.dart و نقوم أولاً بإنشاء نسخة جديدة من الملف الذي قمنا بإنشائه مسبقاً StreamProductsBloc streamProductsBloc = StreamProductsBloc(); ثم ننشأ عدة متغيرات و نعرف TabController لوضع التصنيفات في هذه TabController bool get wantKeepAlive => true; HelperApi helperApi = new HelperApi(); int currentIndex = 0; late TabController _tabController; ثم نقوم بصنع Widget خاصة بعرض التصنيفات في TabController Widget _categories(List<CategoryModel> categoryModel, BuildContext context) { _tabController = TabController( initialIndex: 0, length: categoryModel.length, vsync: this, ); return Scaffold( appBar: AppBar( title: Text(S.of(context).products), bottom: TabBar( indicatorColor: Color(0XFF117182), indicatorWeight: 5, controller: _tabController, tabs: _tabs(categoryModel), isScrollable: true, onTap: (int index) { streamProductsBloc.fetchProducts .add(this.productsCategories[index].id!); }, ), ), body: Container( child: StreamBuilder( stream: streamProductsBloc.productsStream, builder: (context, AsyncSnapshot<List<ProductsModel>>? snapshot) { switch (snapshot!.connectionState) { case ConnectionState.none: // ignore: todo // TODO: Handle this case. return Center( child: CircularProgressIndicator(), ); case ConnectionState.waiting: // ignore: todo // TODO: Handle this case. return ShimmerGrid(); case ConnectionState.done: case ConnectionState.active: // ignore: todo // TODO: Handle this case. if (snapshot.hasError) { return Center( child: ShimmerGrid(), ); } else { if (!snapshot.hasData) { return ShimmerGrid(); } else { return _drawProducts(snapshot.data!, context); } } } }, ), ), ); } لاحظ أننا نقوم بجلب منتجات كل تصنيف عند الضغط عليها عن طريق استخدام id هذا التصنيف streamProductsBloc.fetchProducts.add(this.productsCategories[index].id!); و من ثم نقوم بإضافة هذا الحدث إلى streamProductBloc . ثم نقوم برسم Widget الخاص بالمنتجات Widget _drawProducts(List<ProductsModel> products, BuildContext context) { final cart = Provider.of<Cart>(context, listen: false); var initModel = Provider.of<InitModel>(context, listen: false,); var auth = Provider.of<AuthProvider>(context, listen: false); return Consumer<WishListProvider>( builder: (BuildContext context, wishListData, child) { return Container( padding: EdgeInsets.only(top: 24), child: Column( children: [ Flexible( child: GridView.builder( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 1 / 2, crossAxisSpacing: 20, mainAxisSpacing: 20), itemCount: products.length, itemBuilder: (context, index) { return Card( clipBehavior: Clip.hardEdge, elevation: 0.9, child: AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 375), child: SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation( child: Padding( padding: const EdgeInsets.all(8.0), child: InkWell( onTap: () { _gotoSingleProduct(products[index], context); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( // ignore: unnecessary_null_comparison child: Stack( children: [ (products[index].images![0].src != null) ? Image.network( products[index].images![0].src!, width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.cover, ) : Image.asset( "assets/images/1.jpeg", width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.fill, ), Positioned.directional( textDirection: Directionality.of( context), child: Container( margin: EdgeInsets.symmetric( horizontal: 12.0, vertical: 10.0), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(50.0)), child: IconButton( icon: Icon( Icons.favorite_outline), onPressed: () { //wishListData.getCount(products[index].id!, auth.id!); wishListData.addNewWishList( price: products[index] .price! .toString(), product: products[index] .name!, image: products[index] .images![0] .src!, productId: products[index] .id, userId: auth.id); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .add_product_to_wishlist, ), duration: Duration( seconds: 2), ), ); }, iconSize: 20.0, ), ), ) ], ) ), Padding( padding: const EdgeInsets.all(8.0), child: Text( products[index].name!.substring(0, 10), style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 18.0, ), ), ), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text(initModel.currency! + products[index].price!, style: TextStyle( color: Colors.black, fontSize: 15.0, ), ), ), IconButton( icon: Icon( Icons.add_shopping_cart_outlined), onPressed: () { //if(auth.status == Status.Authenticated){ cart.addItem( products[index].id.toString(), 10.0, products[index].name!.toString(), products[index].images![0].src!, 1, products[index].id.toString()); ScaffoldMessenger.of(context) .hideCurrentSnackBar(); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .product_added_to_the_cart, ), duration: Duration(seconds: 2), action: SnackBarAction( label: S .of(context) .undo, onPressed: () { cart.reomveSingleItem( products[index] .id .toString()); }, ), ), ); }, ), ], ), Text(products[index].stockStatus!, style: TextStyle( color: Colors.orange, fontWeight: FontWeight.bold, fontSize: 15, ),), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ RatingBar.builder( initialRating: products[index] .ratingCount! .toDouble(), minRating: 1, direction: Axis.horizontal, allowHalfRating: true, itemSize: 15.0, itemCount: 5, itemPadding: EdgeInsets.symmetric(horizontal: 4.0), itemBuilder: (context, _) => Icon( Icons.star, color: Colors.amber, ), onRatingUpdate: (rating) { print(rating); }, ), Text(products[index] .ratingCount! .toDouble() .toString()), ], ), ], ), ), ), ), ), ), ); }, ), ), ], ), ); }); } List<Tab> _tabs(List<CategoryModel> categoryModel) { List<Tab> tabs = []; for (CategoryModel category in categoryModel) { tabs.add(Tab( text: category.name, )); } return tabs; } وهذه كامل الملفات , ملف stream_products.dart import 'dart:async'; import '../../common/api_service.dart'; import '../../pages/contracts/contracts.dart'; import '../../models/Product.dart'; class StreamProductsBloc implements Disposable { List<ProductsModel>? products; final StreamController<List<ProductsModel>> _productsController = StreamController<List<ProductsModel>>.broadcast(); final StreamController<int> _categoryController = StreamController<int>.broadcast(); Stream<List<ProductsModel>> get productsStream => _productsController.stream; StreamSink<int> get fetchProducts => _categoryController.sink; Stream<int> get getProductsForCategoriescategory => _categoryController.stream; APIService? apiService; int categoryID = 0; StreamProductsBloc() { //this.categoryID = categoryID; this.products = []; apiService = APIService(); _productsController.add(products!); _categoryController.add(this.categoryID); _categoryController.stream.listen(_fetchCategoriesFromApi); } Future<void> _fetchCategoriesFromApi(int category) async { this.products = await apiService!.getProductsForCategories(category); _productsController.add(this.products!); } @override void dispose(){ _productsController.close(); _categoryController.close(); } } و ملف الواجهة ملف products_screen.dart import 'package:bistore/providers/auth.dart'; import 'package:bistore/services/wishlist_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; import '../../models/init_model.dart'; import '../../widgets/shimmer/shimmer_grid.dart'; import '../../generated/l10n.dart'; import '../../models/cart_model.dart'; import '../products/single_product.dart'; import '../../models/Product.dart'; import '../../models/category.dart'; import '../../utils/helper_api.dart'; import '../../pages/home/stream_products.dart'; class Products extends StatefulWidget { const Products({Key? key}) : super(key: key); @override _ProductsState createState() => _ProductsState(); } class _ProductsState extends State<Products> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { @override bool get wantKeepAlive => true; HelperApi helperApi = new HelperApi(); int currentIndex = 0; late TabController _tabController; var refreshKey = GlobalKey<RefreshIndicatorState>(); StreamProductsBloc streamProductsBloc = StreamProductsBloc(); late List<CategoryModel> productsCategories; @override void initState() { // ignore: todo // TODO: implement initState super.initState(); HelperApi().fetchCategories(1); } @override void dispose() { // ignore: todo // TODO: implement dispose super.dispose(); _tabController.dispose(); streamProductsBloc.dispose(); } @override // ignore: must_call_super Widget build(BuildContext context) { return FutureBuilder<List<CategoryModel>>( future: helperApi.fetchCategories(1), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: return Center( child: Text('No Connection'), ); case ConnectionState.waiting: case ConnectionState.active: return Scaffold( body: Center( child: CircularProgressIndicator(), ), ); case ConnectionState.done: if (snapshot.hasError) { print(snapshot.error); } else { if (!snapshot.hasData) { print('Data Not Found'); } else { this.productsCategories = snapshot.data; streamProductsBloc.fetchProducts .add(this.productsCategories[0].id!); return _categories(snapshot.data, context); } } break; } return Container(); }, ); } Widget _categories(List<CategoryModel> categoryModel, BuildContext context) { _tabController = TabController( initialIndex: 0, length: categoryModel.length, vsync: this, ); return Scaffold( appBar: AppBar( title: Text(S.of(context).products), bottom: TabBar( indicatorColor: Color(0XFF117182), indicatorWeight: 5, controller: _tabController, tabs: _tabs(categoryModel), isScrollable: true, onTap: (int index) { streamProductsBloc.fetchProducts .add(this.productsCategories[index].id!); }, ), ), body: Container( child: StreamBuilder( stream: streamProductsBloc.productsStream, builder: (context, AsyncSnapshot<List<ProductsModel>>? snapshot) { switch (snapshot!.connectionState) { case ConnectionState.none: // ignore: todo // TODO: Handle this case. return Center( child: CircularProgressIndicator(), ); case ConnectionState.waiting: // ignore: todo // TODO: Handle this case. return ShimmerGrid(); case ConnectionState.done: case ConnectionState.active: // ignore: todo // TODO: Handle this case. if (snapshot.hasError) { return Center( child: ShimmerGrid(), ); } else { if (!snapshot.hasData) { return ShimmerGrid(); } else { return _drawProducts(snapshot.data!, context); } } } }, ), ), ); } Widget _drawProducts(List<ProductsModel> products, BuildContext context) { final cart = Provider.of<Cart>(context, listen: false); var initModel = Provider.of<InitModel>(context, listen: false,); var auth = Provider.of<AuthProvider>(context, listen: false); return Consumer<WishListProvider>( builder: (BuildContext context, wishListData, child) { return Container( padding: EdgeInsets.only(top: 24), child: Column( children: [ Flexible( child: GridView.builder( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 1 / 2, crossAxisSpacing: 20, mainAxisSpacing: 20), itemCount: products.length, itemBuilder: (context, index) { return Card( clipBehavior: Clip.hardEdge, elevation: 0.9, child: AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 375), child: SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation( child: Padding( padding: const EdgeInsets.all(8.0), child: InkWell( onTap: () { _gotoSingleProduct(products[index], context); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( // ignore: unnecessary_null_comparison child: Stack( children: [ (products[index].images![0].src != null) ? Image.network( products[index].images![0].src!, width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.cover, ) : Image.asset( "assets/images/1.jpeg", width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.fill, ), Positioned.directional( textDirection: Directionality.of( context), child: Container( margin: EdgeInsets.symmetric( horizontal: 12.0, vertical: 10.0), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(50.0)), child: IconButton( icon: Icon( Icons.favorite_outline), onPressed: () { //wishListData.getCount(products[index].id!, auth.id!); wishListData.addNewWishList( price: products[index] .price! .toString(), product: products[index] .name!, image: products[index] .images![0] .src!, productId: products[index] .id, userId: auth.id); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .add_product_to_wishlist, ), duration: Duration( seconds: 2), ), ); }, iconSize: 20.0, ), ), ) ], ) ), Padding( padding: const EdgeInsets.all(8.0), child: Text( products[index].name!.substring(0, 10), style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 18.0, ), ), ), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text(initModel.currency! + products[index].price!, style: TextStyle( color: Colors.black, fontSize: 15.0, ), ), ), IconButton( icon: Icon( Icons.add_shopping_cart_outlined), onPressed: () { //if(auth.status == Status.Authenticated){ cart.addItem( products[index].id.toString(), 10.0, products[index].name!.toString(), products[index].images![0].src!, 1, products[index].id.toString()); ScaffoldMessenger.of(context) .hideCurrentSnackBar(); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .product_added_to_the_cart, ), duration: Duration(seconds: 2), action: SnackBarAction( label: S .of(context) .undo, onPressed: () { cart.reomveSingleItem( products[index] .id .toString()); }, ), ), ); }, ), ], ), Text(products[index].stockStatus!, style: TextStyle( color: Colors.orange, fontWeight: FontWeight.bold, fontSize: 15, ),), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ RatingBar.builder( initialRating: products[index] .ratingCount! .toDouble(), minRating: 1, direction: Axis.horizontal, allowHalfRating: true, itemSize: 15.0, itemCount: 5, itemPadding: EdgeInsets.symmetric(horizontal: 4.0), itemBuilder: (context, _) => Icon( Icons.star, color: Colors.amber, ), onRatingUpdate: (rating) { print(rating); }, ), Text(products[index] .ratingCount! .toDouble() .toString()), ], ), ], ), ), ), ), ), ), ); }, ), ), ], ), ); }); } List<Tab> _tabs(List<CategoryModel> categoryModel) { List<Tab> tabs = []; for (CategoryModel category in categoryModel) { tabs.add(Tab( text: category.name, )); } return tabs; } void _gotoSingleProduct( ProductsModel singleProductModel, BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => SingleProduct(singleProductModel)), ); } } class ProductsList extends StatelessWidget { final List<ProductsModel> productsModel; ProductsList({Key? key, required this.productsModel}) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ Expanded( child: ListView.builder( scrollDirection: Axis.vertical, itemCount: productsModel.length, itemBuilder: (context, index) { return Text('${productsModel[index].name}'); }, ), ), ], ); } } 1 اقتباس
0 Mohammed Abu Yousef نشر 25 يوليو 2021 الكاتب أرسل تقرير نشر 25 يوليو 2021 بتاريخ منذ ساعة مضت قال بلال زيادة: هناك طريقة وهي استخدام StreamController بحيث هي طريقة من طرق إدارة الحالة في فلاتر. و الأمر معقد جداً و يجب أن تكون على دراية جيدة بكيفية استخدام StreamController. لاحظ بأنه يجب أن تقوم بتعلم StreamController حتى تفهم الإجابة جيداً لأنه في الوهلة الأولى ستجد أن الأمر قد يكون فيه صعوبة يمكنك إنشاء ملف وليكن مسؤول عن التنصت على أي حدث جديد و يعكسه على الواجهة أي يقوم بتنفيذ هذا الحدث على الواجهة و الميزة في ذلك إنها تقوم بتنفيذ الحدث بدون اللجوء إلى بناء نفسها من الصفر و هذا الملف فليكن اسمه StreamProductsBloc.dart ويمكننا تعريف عدة متغيرات في هذا الملف بهذا الشكل List<ProductsModel>? products; final StreamController<List<ProductsModel>> _productsController = StreamController<List<ProductsModel>>.broadcast(); final StreamController<int> _categoryController = StreamController<int>.broadcast(); Stream<List<ProductsModel>> get productsStream => _productsController.stream; StreamSink<int> get fetchProducts => _categoryController.sink; Stream<int> get getProductsForCategoriescategory => _categoryController.stream; APIService? apiService; int categoryID = 0; List<ProductsModel>? products; بحيث أول شيء نقوم بتعريف الكلاس مودل الخاص بالمنتجات. final StreamController<List<ProductsModel>> _productsController = StreamController<List<ProductsModel>>.broadcast(); و من ثم نقوم بتعريف StreamController ليقوم بالتحكم في العملية , ويتم توفير واجهات مختلفة لإنشاء تدفقات أحداث متنوعة. final StreamController<int> _categoryController = StreamController<int>.broadcast(); وحدة تحكم حيث يمكن الاستماع إليه أكثر من مرة . Stream<List<ProductsModel>> get productsStream => _productsController.stream; ثم نقوم بالحصول على البث للمراقبة. StreamSink<int> get fetchProducts => _categoryController.sink; ثم نقوم بإدخال حدث إضافي عن طريق StreamSink. StreamProductsBloc() { //this.categoryID = categoryID; this.products = []; apiService = APIService(); _productsController.add(products!); _categoryController.add(this.categoryID); _categoryController.stream.listen(_fetchCategoriesFromApi); } Future<void> _fetchCategoriesFromApi(int category) async { this.products = await apiService!.getProductsForCategories(category); _productsController.add(this.products!); } @override void dispose(){ _productsController.close(); _categoryController.close(); } نقوم بإنشاء دالة جلب بيانات التصنيف عند تمرير id التصنيف و نقوم بإرجاع جميع المنتجات و من ثم نقوم بإضافة هذه البيانات إلى وحدة التحكم. Future<void> _fetchCategoriesFromApi(int category) async { this.products = await apiService!.getProductsForCategories(category); _productsController.add(this.products!); } ثم نقوم بإضافة المنتجات و id التصنيف إلى وحدة التحكم في دالة StreamProductsBloc ثم نقوم بمراقبة الحدث عن طريق استخدام listen StreamProductsBloc() { //this.categoryID = categoryID; this.products = []; apiService = APIService(); _productsController.add(products!); _categoryController.add(this.categoryID); _categoryController.stream.listen(_fetchCategoriesFromApi); } ثم نقوم بإنشاء ملف واجهة عرض البيانات وليكن مثلاً باسم products_screen.dart و نقوم أولاً بإنشاء نسخة جديدة من الملف الذي قمنا بإنشائه مسبقاً StreamProductsBloc streamProductsBloc = StreamProductsBloc(); ثم ننشأ عدة متغيرات و نعرف TabController لوضع التصنيفات في هذه TabController bool get wantKeepAlive => true; HelperApi helperApi = new HelperApi(); int currentIndex = 0; late TabController _tabController; ثم نقوم بصنع Widget خاصة بعرض التصنيفات في TabController Widget _categories(List<CategoryModel> categoryModel, BuildContext context) { _tabController = TabController( initialIndex: 0, length: categoryModel.length, vsync: this, ); return Scaffold( appBar: AppBar( title: Text(S.of(context).products), bottom: TabBar( indicatorColor: Color(0XFF117182), indicatorWeight: 5, controller: _tabController, tabs: _tabs(categoryModel), isScrollable: true, onTap: (int index) { streamProductsBloc.fetchProducts .add(this.productsCategories[index].id!); }, ), ), body: Container( child: StreamBuilder( stream: streamProductsBloc.productsStream, builder: (context, AsyncSnapshot<List<ProductsModel>>? snapshot) { switch (snapshot!.connectionState) { case ConnectionState.none: // ignore: todo // TODO: Handle this case. return Center( child: CircularProgressIndicator(), ); case ConnectionState.waiting: // ignore: todo // TODO: Handle this case. return ShimmerGrid(); case ConnectionState.done: case ConnectionState.active: // ignore: todo // TODO: Handle this case. if (snapshot.hasError) { return Center( child: ShimmerGrid(), ); } else { if (!snapshot.hasData) { return ShimmerGrid(); } else { return _drawProducts(snapshot.data!, context); } } } }, ), ), ); } لاحظ أننا نقوم بجلب منتجات كل تصنيف عند الضغط عليها عن طريق استخدام id هذا التصنيف streamProductsBloc.fetchProducts.add(this.productsCategories[index].id!); و من ثم نقوم بإضافة هذا الحدث إلى streamProductBloc . ثم نقوم برسم Widget الخاص بالمنتجات Widget _drawProducts(List<ProductsModel> products, BuildContext context) { final cart = Provider.of<Cart>(context, listen: false); var initModel = Provider.of<InitModel>(context, listen: false,); var auth = Provider.of<AuthProvider>(context, listen: false); return Consumer<WishListProvider>( builder: (BuildContext context, wishListData, child) { return Container( padding: EdgeInsets.only(top: 24), child: Column( children: [ Flexible( child: GridView.builder( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 1 / 2, crossAxisSpacing: 20, mainAxisSpacing: 20), itemCount: products.length, itemBuilder: (context, index) { return Card( clipBehavior: Clip.hardEdge, elevation: 0.9, child: AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 375), child: SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation( child: Padding( padding: const EdgeInsets.all(8.0), child: InkWell( onTap: () { _gotoSingleProduct(products[index], context); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( // ignore: unnecessary_null_comparison child: Stack( children: [ (products[index].images![0].src != null) ? Image.network( products[index].images![0].src!, width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.cover, ) : Image.asset( "assets/images/1.jpeg", width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.fill, ), Positioned.directional( textDirection: Directionality.of( context), child: Container( margin: EdgeInsets.symmetric( horizontal: 12.0, vertical: 10.0), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(50.0)), child: IconButton( icon: Icon( Icons.favorite_outline), onPressed: () { //wishListData.getCount(products[index].id!, auth.id!); wishListData.addNewWishList( price: products[index] .price! .toString(), product: products[index] .name!, image: products[index] .images![0] .src!, productId: products[index] .id, userId: auth.id); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .add_product_to_wishlist, ), duration: Duration( seconds: 2), ), ); }, iconSize: 20.0, ), ), ) ], ) ), Padding( padding: const EdgeInsets.all(8.0), child: Text( products[index].name!.substring(0, 10), style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 18.0, ), ), ), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text(initModel.currency! + products[index].price!, style: TextStyle( color: Colors.black, fontSize: 15.0, ), ), ), IconButton( icon: Icon( Icons.add_shopping_cart_outlined), onPressed: () { //if(auth.status == Status.Authenticated){ cart.addItem( products[index].id.toString(), 10.0, products[index].name!.toString(), products[index].images![0].src!, 1, products[index].id.toString()); ScaffoldMessenger.of(context) .hideCurrentSnackBar(); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .product_added_to_the_cart, ), duration: Duration(seconds: 2), action: SnackBarAction( label: S .of(context) .undo, onPressed: () { cart.reomveSingleItem( products[index] .id .toString()); }, ), ), ); }, ), ], ), Text(products[index].stockStatus!, style: TextStyle( color: Colors.orange, fontWeight: FontWeight.bold, fontSize: 15, ),), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ RatingBar.builder( initialRating: products[index] .ratingCount! .toDouble(), minRating: 1, direction: Axis.horizontal, allowHalfRating: true, itemSize: 15.0, itemCount: 5, itemPadding: EdgeInsets.symmetric(horizontal: 4.0), itemBuilder: (context, _) => Icon( Icons.star, color: Colors.amber, ), onRatingUpdate: (rating) { print(rating); }, ), Text(products[index] .ratingCount! .toDouble() .toString()), ], ), ], ), ), ), ), ), ), ); }, ), ), ], ), ); }); } List<Tab> _tabs(List<CategoryModel> categoryModel) { List<Tab> tabs = []; for (CategoryModel category in categoryModel) { tabs.add(Tab( text: category.name, )); } return tabs; } وهذه كامل الملفات , ملف stream_products.dart import 'dart:async'; import '../../common/api_service.dart'; import '../../pages/contracts/contracts.dart'; import '../../models/Product.dart'; class StreamProductsBloc implements Disposable { List<ProductsModel>? products; final StreamController<List<ProductsModel>> _productsController = StreamController<List<ProductsModel>>.broadcast(); final StreamController<int> _categoryController = StreamController<int>.broadcast(); Stream<List<ProductsModel>> get productsStream => _productsController.stream; StreamSink<int> get fetchProducts => _categoryController.sink; Stream<int> get getProductsForCategoriescategory => _categoryController.stream; APIService? apiService; int categoryID = 0; StreamProductsBloc() { //this.categoryID = categoryID; this.products = []; apiService = APIService(); _productsController.add(products!); _categoryController.add(this.categoryID); _categoryController.stream.listen(_fetchCategoriesFromApi); } Future<void> _fetchCategoriesFromApi(int category) async { this.products = await apiService!.getProductsForCategories(category); _productsController.add(this.products!); } @override void dispose(){ _productsController.close(); _categoryController.close(); } } و ملف الواجهة ملف products_screen.dart import 'package:bistore/providers/auth.dart'; import 'package:bistore/services/wishlist_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; import '../../models/init_model.dart'; import '../../widgets/shimmer/shimmer_grid.dart'; import '../../generated/l10n.dart'; import '../../models/cart_model.dart'; import '../products/single_product.dart'; import '../../models/Product.dart'; import '../../models/category.dart'; import '../../utils/helper_api.dart'; import '../../pages/home/stream_products.dart'; class Products extends StatefulWidget { const Products({Key? key}) : super(key: key); @override _ProductsState createState() => _ProductsState(); } class _ProductsState extends State<Products> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { @override bool get wantKeepAlive => true; HelperApi helperApi = new HelperApi(); int currentIndex = 0; late TabController _tabController; var refreshKey = GlobalKey<RefreshIndicatorState>(); StreamProductsBloc streamProductsBloc = StreamProductsBloc(); late List<CategoryModel> productsCategories; @override void initState() { // ignore: todo // TODO: implement initState super.initState(); HelperApi().fetchCategories(1); } @override void dispose() { // ignore: todo // TODO: implement dispose super.dispose(); _tabController.dispose(); streamProductsBloc.dispose(); } @override // ignore: must_call_super Widget build(BuildContext context) { return FutureBuilder<List<CategoryModel>>( future: helperApi.fetchCategories(1), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: return Center( child: Text('No Connection'), ); case ConnectionState.waiting: case ConnectionState.active: return Scaffold( body: Center( child: CircularProgressIndicator(), ), ); case ConnectionState.done: if (snapshot.hasError) { print(snapshot.error); } else { if (!snapshot.hasData) { print('Data Not Found'); } else { this.productsCategories = snapshot.data; streamProductsBloc.fetchProducts .add(this.productsCategories[0].id!); return _categories(snapshot.data, context); } } break; } return Container(); }, ); } Widget _categories(List<CategoryModel> categoryModel, BuildContext context) { _tabController = TabController( initialIndex: 0, length: categoryModel.length, vsync: this, ); return Scaffold( appBar: AppBar( title: Text(S.of(context).products), bottom: TabBar( indicatorColor: Color(0XFF117182), indicatorWeight: 5, controller: _tabController, tabs: _tabs(categoryModel), isScrollable: true, onTap: (int index) { streamProductsBloc.fetchProducts .add(this.productsCategories[index].id!); }, ), ), body: Container( child: StreamBuilder( stream: streamProductsBloc.productsStream, builder: (context, AsyncSnapshot<List<ProductsModel>>? snapshot) { switch (snapshot!.connectionState) { case ConnectionState.none: // ignore: todo // TODO: Handle this case. return Center( child: CircularProgressIndicator(), ); case ConnectionState.waiting: // ignore: todo // TODO: Handle this case. return ShimmerGrid(); case ConnectionState.done: case ConnectionState.active: // ignore: todo // TODO: Handle this case. if (snapshot.hasError) { return Center( child: ShimmerGrid(), ); } else { if (!snapshot.hasData) { return ShimmerGrid(); } else { return _drawProducts(snapshot.data!, context); } } } }, ), ), ); } Widget _drawProducts(List<ProductsModel> products, BuildContext context) { final cart = Provider.of<Cart>(context, listen: false); var initModel = Provider.of<InitModel>(context, listen: false,); var auth = Provider.of<AuthProvider>(context, listen: false); return Consumer<WishListProvider>( builder: (BuildContext context, wishListData, child) { return Container( padding: EdgeInsets.only(top: 24), child: Column( children: [ Flexible( child: GridView.builder( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 1 / 2, crossAxisSpacing: 20, mainAxisSpacing: 20), itemCount: products.length, itemBuilder: (context, index) { return Card( clipBehavior: Clip.hardEdge, elevation: 0.9, child: AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 375), child: SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation( child: Padding( padding: const EdgeInsets.all(8.0), child: InkWell( onTap: () { _gotoSingleProduct(products[index], context); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( // ignore: unnecessary_null_comparison child: Stack( children: [ (products[index].images![0].src != null) ? Image.network( products[index].images![0].src!, width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.cover, ) : Image.asset( "assets/images/1.jpeg", width: MediaQuery .of(context) .size .width, height: MediaQuery .of(context) .size .height, fit: BoxFit.fill, ), Positioned.directional( textDirection: Directionality.of( context), child: Container( margin: EdgeInsets.symmetric( horizontal: 12.0, vertical: 10.0), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(50.0)), child: IconButton( icon: Icon( Icons.favorite_outline), onPressed: () { //wishListData.getCount(products[index].id!, auth.id!); wishListData.addNewWishList( price: products[index] .price! .toString(), product: products[index] .name!, image: products[index] .images![0] .src!, productId: products[index] .id, userId: auth.id); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .add_product_to_wishlist, ), duration: Duration( seconds: 2), ), ); }, iconSize: 20.0, ), ), ) ], ) ), Padding( padding: const EdgeInsets.all(8.0), child: Text( products[index].name!.substring(0, 10), style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 18.0, ), ), ), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text(initModel.currency! + products[index].price!, style: TextStyle( color: Colors.black, fontSize: 15.0, ), ), ), IconButton( icon: Icon( Icons.add_shopping_cart_outlined), onPressed: () { //if(auth.status == Status.Authenticated){ cart.addItem( products[index].id.toString(), 10.0, products[index].name!.toString(), products[index].images![0].src!, 1, products[index].id.toString()); ScaffoldMessenger.of(context) .hideCurrentSnackBar(); ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( S .of(context) .product_added_to_the_cart, ), duration: Duration(seconds: 2), action: SnackBarAction( label: S .of(context) .undo, onPressed: () { cart.reomveSingleItem( products[index] .id .toString()); }, ), ), ); }, ), ], ), Text(products[index].stockStatus!, style: TextStyle( color: Colors.orange, fontWeight: FontWeight.bold, fontSize: 15, ),), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ RatingBar.builder( initialRating: products[index] .ratingCount! .toDouble(), minRating: 1, direction: Axis.horizontal, allowHalfRating: true, itemSize: 15.0, itemCount: 5, itemPadding: EdgeInsets.symmetric(horizontal: 4.0), itemBuilder: (context, _) => Icon( Icons.star, color: Colors.amber, ), onRatingUpdate: (rating) { print(rating); }, ), Text(products[index] .ratingCount! .toDouble() .toString()), ], ), ], ), ), ), ), ), ), ); }, ), ), ], ), ); }); } List<Tab> _tabs(List<CategoryModel> categoryModel) { List<Tab> tabs = []; for (CategoryModel category in categoryModel) { tabs.add(Tab( text: category.name, )); } return tabs; } void _gotoSingleProduct( ProductsModel singleProductModel, BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => SingleProduct(singleProductModel)), ); } } class ProductsList extends StatelessWidget { final List<ProductsModel> productsModel; ProductsList({Key? key, required this.productsModel}) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ Expanded( child: ListView.builder( scrollDirection: Axis.vertical, itemCount: productsModel.length, itemBuilder: (context, index) { return Text('${productsModel[index].name}'); }, ), ), ], ); } } شكرا جدا أستاذ بلال، لكن يبدو أن هناك نقص في الملفات بما أنها تحتوي على أكواد كثيرة تعتمد على ملفات أخرى، هل يمكنك إرسال المشروع كامل حتى أستطيع أخذ الفكرة منه 1 اقتباس
0 بلال زيادة نشر 25 يوليو 2021 أرسل تقرير نشر 25 يوليو 2021 بتاريخ 45 دقائق مضت قال Mohammed Abu Yousef: شكرا جدا أستاذ بلال، لكن يبدو أن هناك نقص في الملفات بما أنها تحتوي على أكواد كثيرة تعتمد على ملفات أخرى، هل يمكنك إرسال المشروع كامل حتى أستطيع أخذ الفكرة منه باقي الملفات الموجودة هي ملفات تكميلية للواجهة و لكن يمكنك الاعتماد على الاكواد الخاصة ب tabbar و widget الخاصة بعرض المنتجات. 1 اقتباس
السؤال
Mohammed Abu Yousef
حاولت كثيرا لكن للأسف لم تعمل، عندي بالصفحة الرئيسية تظهر ال categories
أريد عند الضغط على أي category تظهر ال products الخاصة فيه تحت في نفس الشاشة ( يعني ما في navigation ) حسب الصورة المرفقة
ملاحظة / ال data جاية من API
3 أجوبة على هذا السؤال
Recommended Posts
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.