import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:line_icons/line_icons.dart'; import '../../../common/theme/theme.dart'; import '../../components/button/button.dart'; import 'widgets/appbar.dart'; import 'widgets/category_delegate.dart'; import 'widgets/product_tile.dart'; @RoutePage() class ProductPage extends StatefulWidget { const ProductPage({super.key}); @override State createState() => _ProductPageState(); } enum ViewType { grid, list } class _ProductPageState extends State with TickerProviderStateMixin { String selectedCategory = 'Semua'; List categories = ['Semua', 'Makanan', 'Minuman', 'Snack', 'Dessert']; ViewType currentViewType = ViewType.grid; // Animation late AnimationController _rotationController; late Animation _rotationAnimation; // Sample product data List products = [ Product( id: '1', name: 'Nasi Goreng Special', price: 25000, category: 'Makanan', stock: 50, imageUrl: 'assets/images/nasi_goreng.jpg', isActive: true, ), Product( id: '8', name: 'Nasi Goreng', price: 15000, category: 'Makanan', stock: 50, imageUrl: 'assets/images/nasi_goreng.jpg', isActive: true, ), Product( id: '9', name: 'Nasi Goreng Telor', price: 18000, category: 'Makanan', stock: 50, imageUrl: 'assets/images/nasi_goreng.jpg', isActive: true, ), Product( id: '10', name: 'Mie Goreng ', price: 18000, category: 'Makanan', stock: 50, imageUrl: 'assets/images/nasi_goreng.jpg', isActive: true, ), Product( id: '2', name: 'Es Teh Manis', price: 8000, category: 'Minuman', stock: 100, imageUrl: 'assets/images/es_teh.jpg', isActive: true, ), Product( id: '6', name: 'Es Jeruk', price: 10000, category: 'Minuman', stock: 100, imageUrl: 'assets/images/es_teh.jpg', isActive: true, ), Product( id: '7', name: 'Es Kelapa', price: 12000, category: 'Minuman', stock: 100, imageUrl: 'assets/images/es_teh.jpg', isActive: true, ), Product( id: '3', name: 'Keripik Singkong', price: 15000, category: 'Snack', stock: 25, imageUrl: 'assets/images/keripik.jpg', isActive: true, ), Product( id: '4', name: 'Es Krim Vanilla', price: 12000, category: 'Dessert', stock: 30, imageUrl: 'assets/images/ice_cream.jpg', isActive: false, ), Product( id: '5', name: 'Ayam Bakar', price: 35000, category: 'Makanan', stock: 20, imageUrl: 'assets/images/ayam_bakar.jpg', isActive: true, ), ]; List get filteredProducts { return products.where((product) { bool matchesCategory = selectedCategory == 'Semua' || product.category == selectedCategory; return matchesCategory; }).toList(); } @override initState() { super.initState(); _rotationController = AnimationController( duration: const Duration(seconds: 3), vsync: this, )..repeat(); _rotationAnimation = Tween( begin: 0, end: 2 * math.pi, ).animate(_rotationController); } @override void dispose() { _rotationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.background, body: CustomScrollView( slivers: [ _buildSliverAppBar(), _buildCategoryFilter(), _buildProductContent(), _buildEmptyState(), ], ), ); } Widget _buildSliverAppBar() { return SliverAppBar( expandedHeight: 120.0, floating: false, pinned: true, elevation: 0, flexibleSpace: ProductAppbar(rotationAnimation: _rotationAnimation), actions: [ ActionIconButton(onTap: () {}, icon: LineIcons.search), ActionIconButton(onTap: _showAddProductDialog, icon: LineIcons.plus), ActionIconButton( onTap: _toggleViewType, icon: currentViewType == ViewType.grid ? LineIcons.list : LineIcons.thLarge, ), ActionIconButton(onTap: _showOptionsMenu, icon: LineIcons.filter), ], ); } Widget _buildCategoryFilter() { return SliverPersistentHeader( pinned: true, delegate: ProductCategoryHeaderDelegate( categories: categories, selectedCategory: selectedCategory, onCategoryChanged: (category) { setState(() { selectedCategory = category; }); }, ), ); } Widget _buildProductContent() { if (filteredProducts.isEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); } return currentViewType == ViewType.grid ? _buildProductGrid() : _buildProductList(); } Widget _buildProductGrid() { 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 = filteredProducts[index]; return ProductTile(product: product, onTap: () {}); }, childCount: filteredProducts.length), ), ); } Widget _buildProductList() { return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { final product = filteredProducts[index]; return _buildProductListItem(product); }, childCount: filteredProducts.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( product.category, 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: ${product.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() { if (filteredProducts.isNotEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); } return SliverToBoxAdapter( child: Container( height: 300, margin: const EdgeInsets.all(32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.inventory_2_outlined, size: 64, color: AppColor.textLight, ), const SizedBox(height: 16), Text( 'Tidak ada produk ditemukan', style: TextStyle( color: AppColor.textSecondary, fontSize: 16, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), Text( 'Coba ubah filter atau tambah produk baru', style: TextStyle(color: AppColor.textLight, fontSize: 14), textAlign: TextAlign.center, ), ], ), ), ); } void _showAddProductDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Tambah Produk'), content: const Text( 'Dialog tambah produk akan diimplementasikan di sini', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Batal'), ), ElevatedButton( onPressed: () => Navigator.pop(context), child: const Text('Simpan'), ), ], ), ); } void _showOptionsMenu() { showModalBottomSheet( context: context, builder: (context) => Container( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: Icon( currentViewType == ViewType.grid ? Icons.list : Icons.grid_view, ), title: Text( currentViewType == ViewType.grid ? 'Tampilan List' : 'Tampilan Grid', ), onTap: () { Navigator.pop(context); _toggleViewType(); }, ), ListTile( leading: const Icon(Icons.sort), title: const Text('Urutkan'), onTap: () => Navigator.pop(context), ), ListTile( leading: const Icon(Icons.filter_list), title: const Text('Filter Lanjutan'), onTap: () => Navigator.pop(context), ), ListTile( leading: const Icon(Icons.download), title: const Text('Export Data'), onTap: () => Navigator.pop(context), ), ], ), ), ); } } // Product Model class Product { final String id; final String name; final int price; final String category; final int stock; final String imageUrl; bool isActive; Product({ required this.id, required this.name, required this.price, required this.category, required this.stock, required this.imageUrl, required this.isActive, }); }