import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:nearledaily/constants/color_constants.dart'; import 'package:speech_to_text/speech_to_text.dart'; import '../../controllers/search_controller/search_controller.dart'; import '../../widgets/search_widgets.dart'; // ─── Main Screen ────────────────────────────────────────────────────────────── class SearchScreen extends StatefulWidget { final bool autoListen; const SearchScreen({super.key,this.autoListen = false}); @override State createState() => _SearchScreenState(); } class _SearchScreenState extends State with TickerProviderStateMixin { late final GrocerySearchController _ctrl; final SpeechToText _speech = SpeechToText(); final TextEditingController _searchController = TextEditingController(); bool _isListening = true; @override void initState() { super.initState(); _ctrl = GrocerySearchController(); _ctrl.init(this); _ctrl.addListener(() => setState(() {})); WidgetsBinding.instance.addPostFrameCallback((_) { if (widget.autoListen) { startListening(); } }); } Future startListening() async { bool available = await _speech.initialize(); if (available) { _speech.listen( onResult: (result) { _searchController.text = result.recognizedWords; }, ); } } @override void dispose() { _ctrl.disposeAll(); super.dispose(); } // ─── Build ────────────────────────────────────────────────────────────────── @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF4F6F3), body: Stack( children: [ // Center( // child: Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // SizedBox( // height: 220, // width: 220, // child: Lottie.asset( // 'assets/lotties/ai.json', // repeat: true, // ), // ), // // const SizedBox(height: 20), // // Text( // "Listening...", // style: TextStyle( // fontSize: 24, // fontWeight: FontWeight.w600, // ), // ), // // const SizedBox(height: 8), // // Text( // "Speak what you're looking for", // style: TextStyle( // color: Colors.grey, // fontSize: 16, // ), // ), // ], // ), // ), CustomScrollView( slivers: [ _buildSliverHeader(), SliverToBoxAdapter(child: _buildSearchBar()), SliverToBoxAdapter( child: AnimatedSwitcher( duration: const Duration(milliseconds: 250), child: _ctrl.hasSearched ? _buildResults() : _buildSuggestions(), ), ), const SliverToBoxAdapter(child: SizedBox(height: 100)), ], ), _buildCartBar(), ], ), ); } // ─── Header ───────────────────────────────────────────────────────────────── Widget _buildSliverHeader() { return SliverAppBar( pinned: true, backgroundColor: Color(0xFFF4F6F3), elevation: 0, flexibleSpace: FlexibleSpaceBar( background: Container(color: Color(0xFFF4F6F3)), ), leading: IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.arrow_back, color: Colors.black), ), title: const Text( 'Search', style: TextStyle(color: Colors.black, fontWeight: FontWeight.w700), ), ); } // ─── Search Bar ───────────────────────────────────────────────────────────── Widget _buildSearchBar() { return Transform.translate( offset: const Offset(0, -20), child: Padding( padding: const EdgeInsets.only(top: 30), child: Container( margin: const EdgeInsets.fromLTRB(16, 0, 16, 0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFE2E8E0), width: 1), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 11, offset: const Offset(0, 2), ), ], ), child: Row( children: [ const Padding( padding: EdgeInsets.only(left: 14, right: 8), child: Icon(Icons.search_rounded, color: Color(0xFF6B7A72), size: 22), ), Expanded( child: TextField( controller: _ctrl.textController, focusNode: _ctrl.focusNode, onSubmitted: _ctrl.search, textInputAction: TextInputAction.search, style: const TextStyle( fontSize: 15, color: Color(0xFF1A1F1C)), decoration: const InputDecoration( hintText: 'Ask anything — biryani, cold remedy...', hintStyle: TextStyle(color: Color(0xFF6B7A72), fontSize: 14), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.symmetric(vertical: 14), ), ), ), if (_ctrl.textController.text.isNotEmpty) IconButton( icon: const Icon(Icons.clear_rounded, color: Color(0xFF6B7A72), size: 18), onPressed: _ctrl.clearSearch, ), Container( margin: const EdgeInsets.all(6), child: _ctrl.loading ? SizedBox( width: 50, height: 50, child: Lottie.asset( 'assets/lotties/ai_loader.json', repeat: true, ), ) : ElevatedButton.icon( onPressed: () => _ctrl.search(_ctrl.textController.text), icon: const Icon( Icons.auto_awesome_rounded, size: 14, ), label: const Text('Find'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF662582), foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(11), ), ), ), ) ], ), ), ), ); } // ─── Suggestions ──────────────────────────────────────────────────────────── Widget _buildSuggestions() { return Padding( key: const ValueKey('suggestions'), padding: const EdgeInsets.fromLTRB(16, 4, 16, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SectionLabel('Quick picks'), const SizedBox(height: 10), Wrap( spacing: 8, runSpacing: 8, children: _ctrl.quickChips .map((c) => SearchChip( label: c['label']!, onTap: () => _ctrl.fillSearch(c['query']!), )) .toList(), ), const SizedBox(height: 18), const SectionLabel('Or tell us naturally'), const SizedBox(height: 10), Wrap( spacing: 8, runSpacing: 8, children: _ctrl.naturalChips .map((c) => SearchChip( label: c['label']!, onTap: () => _ctrl.fillSearch(c['query']!), muted: true, )) .toList(), ), ], ), ); } // ─── Results ──────────────────────────────────────────────────────────────── Widget _buildResults() { return Padding( key: const ValueKey('results'), padding: const EdgeInsets.fromLTRB(16, 4, 16, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Query label const SizedBox(height: 10), // Loading skeletons if (_ctrl.loading) GridView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: 4, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 0.88, ), itemBuilder: (_, __) => const SkeletonCard(), ), // Results header + add-all if (!_ctrl.loading && _ctrl.items.isNotEmpty) ...[ Row( children: [ const Text( 'Products to buy', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1F1C)), ), const Spacer(), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2), decoration: BoxDecoration( color: const Color(0xFFEDF2F0), borderRadius: BorderRadius.circular(10), ), child: Text( '${_ctrl.items.length} items', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF6B7A72)), ), ), ], ), const SizedBox(height: 10), GestureDetector( onTap: _ctrl.allInCart ? null : _ctrl.addAllToCart, child: AnimatedContainer( duration: const Duration(milliseconds: 250), width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: _ctrl.allInCart ? const Color(0xFFE8F5EE) : const Color(0xFF1A6B3C), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( _ctrl.allInCart ? Icons.check_circle_rounded : Icons.add_shopping_cart_rounded, color: _ctrl.allInCart ? const Color(0xFF1A6B3C) : Colors.white, size: 18, ), const SizedBox(width: 6), Text( _ctrl.allInCart ? 'All items in cart' : 'Add all to cart', style: TextStyle( color: _ctrl.allInCart ? const Color(0xFF1A6B3C) : Colors.white, fontSize: 14, fontWeight: FontWeight.w600, ), ), ], ), ), ), const SizedBox(height: 12), GridView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: _ctrl.items.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 0.88, ), itemBuilder: (context, index) => GroceryCard( item: _ctrl.items[index], onToggle: () => _ctrl.toggleCart(_ctrl.items[index]), ), ), ], // Empty state if (!_ctrl.loading && _ctrl.items.isEmpty && _ctrl.hasSearched) Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 40), child: Column( children: [ const Icon(Icons.sentiment_dissatisfied_rounded, size: 40, color: Color(0xFFC0CFC5)), const SizedBox(height: 12), Text( 'No products found.\nTry a different search.', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, color: Colors.grey.shade500, height: 1.6), ), ], ), ), ), ], ), ); } // ─── Cart Bar ──────────────────────────────────────────────────────────────── Widget _buildCartBar() { return Positioned( bottom: 0, left: 0, right: 0, child: SlideTransition( position: _ctrl.cartBarSlide, child: Container( padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), decoration: const BoxDecoration( color: Colors.white, border: Border( top: BorderSide(color: Color(0xFFE2E8E0), width: 1.5)), ), child: Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF1A6B3C), borderRadius: BorderRadius.circular(20), ), child: Text( '${_ctrl.cartItems.length}', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13), ), ), const SizedBox(width: 10), const Text( 'items in cart', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1A1F1C)), ), const Spacer(), ElevatedButton.icon( onPressed: _showCartSheet, icon: const Icon(Icons.arrow_forward_rounded, size: 16), label: const Text('Checkout'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF1A6B3C), foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 11), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600), ), ), ], ), ), ), ); } void _showCartSheet() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (_) => CartSheet( items: _ctrl.cartItems, onRemove: (item) { _ctrl.removeFromCart(item); if (_ctrl.cartItems.isEmpty) Navigator.pop(context); }, ), ); } }