import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; import '../../../sample/shop_sample_data.dart'; import '../../components/field/field.dart'; import '../../router/app_router.gr.dart'; import 'shop_ui.dart'; import 'widgets/shop_category_grid.dart'; import 'widgets/shop_product_card.dart'; import 'widgets/shop_promo_banner.dart'; @RoutePage() class ShopPage extends StatefulWidget { const ShopPage({super.key}); @override State createState() => _ShopPageState(); } class _ShopPageState extends State { static const double _rowCardWidth = 150; final _searchController = TextEditingController(); final _bannerController = PageController(viewportFraction: 0.9); int _bannerIndex = 0; final List _promos = shopPromoSamples; /// Terlaris di atas. final List _products = shopSamples ..sort((a, b) => b.soldCount.compareTo(a.soldCount)); @override void initState() { super.initState(); _searchController.addListener(() => setState(() {})); } @override void dispose() { _searchController.dispose(); _bannerController.dispose(); super.dispose(); } void _openProduct(ShopProduct product) => context.router.push(ShopProductDetailRoute(product: product)); void _openCategory({ShopCategory? category, bool promoOnly = false}) => context.router.push( ShopCategoryRoute(category: category, promoOnly: promoOnly), ); @override Widget build(BuildContext context) { final query = _searchController.text.trim().toLowerCase(); return Scaffold( backgroundColor: AppColor.background, appBar: AppBar(title: const Text('EnakShop')), body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.only(top: 12), child: SearchTextField( hintText: 'Cari di EnakShop', prefixIcon: Icons.search, controller: _searchController, ), ), ), if (query.isNotEmpty) ..._buildSearchResults(query) else ...[ SliverToBoxAdapter(child: _buildBanners()), const SliverToBoxAdapter(child: _SectionTitle(title: 'Kategori')), SliverToBoxAdapter( child: ShopCategoryGrid( onTap: (category) => _openCategory(category: category), ), ), SliverToBoxAdapter( child: _SectionTitle( title: 'Lagi Promo', onSeeAll: () => _openCategory(promoOnly: true), ), ), SliverToBoxAdapter( child: _buildProductRow( _products.where((p) => p.isDiscounted).toList(), ), ), const SliverToBoxAdapter( child: _SectionTitle(title: 'Rekomendasi'), ), _buildGrid(_products), ], const SliverToBoxAdapter(child: SizedBox(height: 24)), ], ), ); } List _buildSearchResults(String query) { final results = _products .where( (p) => p.name.toLowerCase().contains(query) || p.category.label.toLowerCase().contains(query), ) .toList(); if (results.isEmpty) { return [ SliverFillRemaining( hasScrollBody: false, child: Padding( padding: const EdgeInsets.fromLTRB(32, 32, 32, 64), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('🔍', style: TextStyle(fontSize: 44)), const SizedBox(height: 16), Text( 'Produk tidak ditemukan', style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: 6), Text( 'Coba kata kunci lain, mis. "sambal" atau "kopi".', textAlign: TextAlign.center, style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), ], ), ), ), ]; } return [ SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Text( '${results.length} produk ditemukan', style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), ), ), _buildGrid(results), ]; } Widget _buildBanners() { return Column( children: [ SizedBox( // Ikut skala font sistem supaya teks banner tidak terpotong. height: MediaQuery.textScalerOf(context).scale(150), child: PageView.builder( controller: _bannerController, itemCount: _promos.length, onPageChanged: (index) => setState(() => _bannerIndex = index), itemBuilder: (context, index) { final promo = _promos[index]; return Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: ShopPromoBanner( promo: promo, onTap: () => _openCategory(category: promo.category), ), ); }, ), ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(_promos.length, (index) { final isActive = index == _bannerIndex; return AnimatedContainer( duration: const Duration(milliseconds: 200), margin: const EdgeInsets.symmetric(horizontal: 3), width: isActive ? 18 : 6, height: 6, decoration: BoxDecoration( color: isActive ? AppColor.primary : AppColor.border, borderRadius: BorderRadius.circular(3), ), ); }), ), ], ); } Widget _buildProductRow(List products) { return SizedBox( height: _rowCardWidth + shopCardInfoHeight(context), child: ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: products.length, separatorBuilder: (_, _) => const SizedBox(width: 12), itemBuilder: (context, index) { final product = products[index]; return SizedBox( width: _rowCardWidth, child: ShopProductCard( product: product, onTap: () => _openProduct(product), ), ); }, ), ); } Widget _buildGrid(List products) { return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16), sliver: SliverGrid.builder( gridDelegate: shopProductGridDelegate(context), itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; return ShopProductCard( product: product, onTap: () => _openProduct(product), ); }, ), ); } } class _SectionTitle extends StatelessWidget { const _SectionTitle({required this.title, this.onSeeAll}); final String title; final VoidCallback? onSeeAll; @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.fromLTRB(16, 20, onSeeAll == null ? 16 : 4, 10), child: Row( children: [ Expanded( child: Text( title, style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), ), ), if (onSeeAll != null) TextButton( onPressed: onSeeAll, child: Text( 'Lihat semua', style: AppStyle.sm.copyWith( color: AppColor.primary, fontWeight: FontWeight.w700, ), ), ), ], ), ); } }