import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:niogu_ecommerce_v1/core/constant/app_font_size.dart'; import 'package:niogu_ecommerce_v1/core/errors/exceptions.dart'; import 'package:niogu_ecommerce_v1/core/providers/app_provider.dart'; import 'package:niogu_ecommerce_v1/core/router/app_route.dart'; import 'package:niogu_ecommerce_v1/core/utils/currency_format.dart'; import 'package:niogu_ecommerce_v1/core/utils/extenstion_format.dart'; import 'package:niogu_ecommerce_v1/core/utils/log_message.dart'; import 'package:niogu_ecommerce_v1/core/widgets/custom_empty_screen.dart'; import 'package:niogu_ecommerce_v1/core/widgets/custom_snackbar.dart'; import 'package:niogu_ecommerce_v1/features/favorite/domain/entities/favorite.dart'; import 'package:niogu_ecommerce_v1/features/favorite/presentation/providers/favorite_provider.dart'; import 'package:niogu_ecommerce_v1/features/home/domain/entities/home.dart'; import 'package:niogu_ecommerce_v1/features/home/presentation/providers/home_provider.dart'; import 'package:niogu_ecommerce_v1/features/product/presentation/providers/product_provider.dart'; import 'package:sizer/sizer.dart'; import 'package:niogu_ecommerce_v1/core/constant/app_color.dart'; class SearchScreen extends ConsumerStatefulWidget { final List categories; const SearchScreen({super.key, required this.categories}); @override ConsumerState createState() => _SearchScreenState(); } class _SearchScreenState extends ConsumerState { late final List _categories; final TextEditingController _searchController = TextEditingController(); Timer? _debounce; @override void initState() { // TODO: implement initState super.initState(); _categories = widget.categories; } @override void dispose() { _searchController.dispose(); _debounce?.cancel(); super.dispose(); } void _onSearchChanged(String query) { if (_debounce?.isActive ?? false) _debounce?.cancel(); _debounce = Timer(const Duration(milliseconds: 800), () { final processSerch = ref.read(processSearchStateProvider); if (processSerch != null) { ref.read(processSearchStateProvider.notifier).state = null; } ref.read(productSearchProvider.notifier).state = query; }); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final productByNameState = ref.watch(productByNameProvider); final processSearchState = ref.watch(processSearchProvider); final currentOutletId = ref.watch(currentOutletIdProvider); final favoriteState = ref.watch(favoriteControllerProvider); return SafeArea( top: false, bottom: true, right: false, left: false, child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, elevation: 0.5, leading: IconButton( icon: Icon( Icons.arrow_back, size: 7.w, color: AppColor.primaryColor, ), onPressed: () => context.pop(), ), title: Container( height: 5.h, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(2.w), ), child: TextField( controller: _searchController, autofocus: true, onChanged: _onSearchChanged, style: TextStyle(fontSize: AppFontSize.small.sp), decoration: InputDecoration( hintText: "Ketik nama produk...", hintStyle: TextStyle(fontSize: AppFontSize.small.sp), prefixIcon: Icon(Icons.search, size: 5.w), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: Icon( Icons.cancel, color: Colors.grey, size: 5.w, ), onPressed: () { _searchController.clear(); _onSearchChanged(""); }, ) : null, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 1.h), ), ), ), ), body: productByNameState.when( data: (products) { switch (processSearchState) { case ProcessSearch.initial: return _buildInitialRecommended(); case ProcessSearch.suggestion: return _buildSuggestionList(products); case ProcessSearch.result: return _buildProductResult( products: products, currentOutletId: currentOutletId!, favorites: favoriteState, ); } }, error: (error, stackTrace) { return SizedBox( height: 80.h, child: CustomEmptyScreen( icon: Icons.cloud_off_outlined, title: "Terjadi Kesalahan Koneksi", subtitle: "Tarik ke bawah untuk mencoba lagi", height: 80.h, ), ); }, loading: () => const Center( child: CircularProgressIndicator(color: AppColor.primaryColor), ), ), ), ); }, ); } Widget _buildInitialRecommended() { return Padding( padding: EdgeInsets.all(4.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Rekomendasi", style: TextStyle( fontWeight: FontWeight.bold, fontSize: AppFontSize.small.sp, ), ), SizedBox(height: 2.h), GridView.builder( shrinkWrap: true, itemCount: _categories.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, mainAxisSpacing: 2.h, childAspectRatio: 0.8, ), itemBuilder: (context, index) { final category = _categories[index]; return GestureDetector( onTap: () { context.pushNamed( AppRoute.productCategoryScreen, extra: category, ); }, child: Padding( padding: EdgeInsets.only(right: 5.w), child: Column( children: [ CachedNetworkImage( imageUrl: category.image ?? 'error', imageBuilder: (context, imageProvider) { return Container( height: 18.w, width: 18.w, decoration: BoxDecoration( color: Colors.white, border: BoxBorder.all( color: Colors.grey.shade300, ), borderRadius: BorderRadius.circular(2.5.w), image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, ), ], ), ); }, errorWidget: (context, url, error) { return Container( height: 18.w, width: 18.w, decoration: BoxDecoration( color: Colors.white, border: BoxBorder.all( color: Colors.grey.shade300, ), borderRadius: BorderRadius.circular(2.5.w), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, ), ], ), child: Icon( Icons.image_outlined, size: 5.w, color: Colors.grey.shade300, ), ); }, ), SizedBox(height: 1.h), Text( category.name, style: TextStyle( fontSize: (AppFontSize.small - 2).sp, color: Color(0xFF102851), ), ), ], ), ), ); }, ), ], ), ); } Widget _buildSuggestionList(List products) { if (products.isEmpty) { return CustomEmptyScreen( icon: Icons.search_off_outlined, title: "Produk tidak tersedia", height: 40.h, ); } return ListView.builder( itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; return ListTile( leading: Icon(Icons.search, color: Colors.grey, size: 5.w), title: Text( product.name, style: TextStyle( color: Colors.grey.shade700, fontSize: AppFontSize.small.sp, ), ), trailing: Icon(Icons.north_west, color: Colors.grey, size: 5.w), onTap: () { ref.read(processSearchStateProvider.notifier).state = ProcessSearch.result; _searchController.text = product.name; }, ); }, ); } Widget _buildProductResult({ required List products, required String currentOutletId, required Map favorites, }) { return GridView.builder( padding: EdgeInsets.all(4.w), itemCount: products.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.7, crossAxisSpacing: 3.w, mainAxisSpacing: 3.w, ), itemBuilder: (context, index) { final product = products[index]; final isFavorite = favorites.containsKey( "$currentOutletId-${product.id}", ); return _buildProductCard(product, currentOutletId, isFavorite); }, ); } Widget _buildProductCard( ProductItem product, String currentOutletId, bool isFavorite, ) { return GestureDetector( onTap: () async { try { final productDetail = await ref .read(productRepositoryProvider) .fetchProductById(product.id); if (productDetail == null) { CustomSnackbar.showError(context, "Produk tidak ditemukan"); ref.read(homeControllerProvider.notifier).refresh(); return; } context.pushNamed(AppRoute.productDetailScreen, extra: productDetail); } on ServerException catch (e, st) { LogMessage.log.i(e.toString(), error: e, stackTrace: st); } }, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.5.w), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 3, child: CachedNetworkImage( imageUrl: product.image ?? 'error', imageBuilder: (context, imageProvider) { return Container( width: double.infinity, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.vertical( top: Radius.circular(2.5.w), ), image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), child: Stack( children: [ Positioned( top: 1.w, right: 1.w, child: GestureDetector( onTap: () { final currentOutletId = ref.read( currentOutletIdProvider, ); ref .read(favoriteControllerProvider.notifier) .toggle(product, currentOutletId!); }, child: CircleAvatar( radius: 14, backgroundColor: Colors.white.withOpacity(0.9), child: Icon( isFavorite ? Icons.favorite : Icons.favorite_outline, size: 5.w, color: isFavorite ? Colors.red : Colors.grey, ), ), ), ), ], ), ); }, errorWidget: (context, url, error) { return Container( width: double.infinity, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.vertical( top: Radius.circular(2.5.w), ), ), child: Stack( children: [ Center( child: Icon( Icons.image_outlined, color: Colors.grey.shade300, size: 10.w, ), ), Positioned( top: 1.w, right: 1.w, child: GestureDetector( onTap: () { ref .read(favoriteControllerProvider.notifier) .toggle(product, currentOutletId); }, child: CircleAvatar( radius: 14, backgroundColor: Colors.white.withOpacity(0.9), child: Icon( isFavorite ? Icons.favorite : Icons.favorite_outline, size: 5.w, color: isFavorite ? Colors.red : Colors.grey, ), ), ), ), ], ), ); }, ), ), Expanded( flex: 2, child: Padding( padding: EdgeInsets.all(3.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( product.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.bold, fontSize: (AppFontSize.small - 1.25).sp, color: const Color(0xFF102851), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${product.totalSold.toCompact} Terjual", style: TextStyle( color: Colors.black87, fontWeight: FontWeight.bold, fontSize: (AppFontSize.small - 1.25).sp, ), ), Row( children: [ Icon(Icons.star, color: Colors.orange, size: 3.5.w), SizedBox(width: 1.w), Text( product.averageRating.toStringAsFixed(1), style: TextStyle( fontSize: (AppFontSize.small - 1.25).sp, color: Colors.grey.shade600, ), ), ], ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( CurrencyFormat.formatToIdr(product.sellingPrice, 0), style: TextStyle( fontWeight: FontWeight.bold, fontSize: AppFontSize.small.sp, color: AppColor.primaryColor, ), ), Row( children: [ Icon( Icons.thumb_up, color: Colors.red, size: 3.5.w, ), SizedBox(width: 1.w), Text( product.likes.toString(), style: TextStyle( fontSize: (AppFontSize.small - 1.25).sp, color: Colors.red, ), ), ], ), ], ), ], ), ), ), ], ), ), ); } }