import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:line_icons/line_icons.dart'; import '../../../application/category/category_loader/category_loader_bloc.dart'; import '../../../application/product/product_loader/product_loader_bloc.dart'; import '../../../common/theme/theme.dart'; import '../../../domain/category/category.dart'; import '../../../domain/product/product.dart'; import '../../../injection.dart'; import '../../components/appbar/appbar.dart'; import '../../components/button/button.dart'; import '../../components/widgets/empty_widget.dart'; import 'widgets/category_delegate.dart'; import 'widgets/product_tile.dart'; @RoutePage() class ProductPage extends StatefulWidget implements AutoRouteWrapper { const ProductPage({super.key}); @override State createState() => _ProductPageState(); @override Widget wrappedRoute(BuildContext context) => MultiBlocProvider( providers: [ BlocProvider( create: (context) => getIt()..add(CategoryLoaderEvent.fetched()), ), BlocProvider( create: (context) => getIt() ..add(ProductLoaderEvent.fetched(isRefresh: true)), ), ], child: this, ); } enum ViewType { grid, list } class _ProductPageState extends State with TickerProviderStateMixin { Category selectedCategory = Category.addAllData(); ViewType currentViewType = ViewType.grid; @override initState() { super.initState(); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { return Scaffold( backgroundColor: AppColor.background, body: CustomScrollView( slivers: [ _buildSliverAppBar(), _buildCategoryFilter(), _buildProductContent(state.products), _buildEmptyState(state.products), ], ), ); }, ); } Widget _buildSliverAppBar() { return SliverAppBar( expandedHeight: 120.0, floating: false, pinned: true, elevation: 0, flexibleSpace: CustomAppBar(title: 'Produk'), actions: [ ActionIconButton(onTap: () {}, icon: LineIcons.search), ActionIconButton( onTap: _toggleViewType, icon: currentViewType == ViewType.grid ? LineIcons.list : LineIcons.thLarge, ), ], ); } Widget _buildCategoryFilter() { return BlocBuilder( builder: (context, state) { return SliverPersistentHeader( pinned: true, delegate: ProductCategoryHeaderDelegate( categories: state.categories, selectedCategory: selectedCategory, onCategoryChanged: (category) { setState(() { selectedCategory = category; }); }, ), ); }, ); } Widget _buildProductContent(List products) { if (products.isEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); } return currentViewType == ViewType.grid ? _buildProductGrid(products) : _buildProductList(products); } Widget _buildProductGrid(List products) { return SliverPadding( padding: const EdgeInsets.all(16.0), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.85, crossAxisSpacing: 16.0, mainAxisSpacing: 16.0, ), delegate: SliverChildBuilderDelegate((context, index) { final product = products[index]; return ProductTile(product: product, onTap: () {}); }, childCount: products.length), ), ); } Widget _buildProductList(List products) { return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { final product = products[index]; return _buildProductListItem(product); }, childCount: products.length), ), ); } Widget _buildProductListItem(Product product) { return Container( margin: const EdgeInsets.only(bottom: 12.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), spreadRadius: 1, blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: InkWell( onTap: () {}, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(12.0), child: Row( children: [ // Product Image Container( width: 80, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: AppColor.background, ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.asset( product.imageUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( color: AppColor.background, child: Icon( Icons.image_outlined, color: AppColor.textLight, size: 32, ), ); }, ), ), ), const SizedBox(width: 12), // Product Info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( product.name, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( '', style: TextStyle(fontSize: 12, color: AppColor.textLight), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Rp ${_formatPrice(product.price)}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColor.primary, ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: product.isActive ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( 'Stock: ', style: TextStyle( fontSize: 12, color: product.isActive ? Colors.green : Colors.red, fontWeight: FontWeight.w500, ), ), ), ], ), ], ), ), // Action Button IconButton( onPressed: () {}, icon: const Icon(Icons.more_vert), color: AppColor.textLight, ), ], ), ), ), ); } String _formatPrice(int price) { return price.toString().replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.', ); } void _toggleViewType() { setState(() { currentViewType = currentViewType == ViewType.grid ? ViewType.list : ViewType.grid; }); } Widget _buildEmptyState(List products) { if (products.isNotEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); } return SliverToBoxAdapter( child: EmptyWidget( title: 'Tidak ada produk ditemukan', message: 'Coba ubah filter atau tambah produk baru', ), ); } }