Untitled

 avatar
unknown
plain_text
a month ago
22 kB
5
Indexable
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:hsl/models/product.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../providers/product_provider.dart';
import 'home_screen.dart';
import 'product_detail_page.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  _SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  TextEditingController _searchController = TextEditingController();
  String _searchQuery = '';
  List<String> selectedCategories = [];
  double minPrice = 0.0;
  double maxPrice = 1000.0;
  List<Product> filteredProducts = [];
  List<Product> searchProduct = [];
  ScrollController _scrollController = ScrollController();

  int _currentPage = 1;
  bool _isLoading = false;
  bool _hasMoreProducts = true;
  
  @override
  void initState() {
    super.initState();
    _searchController.addListener(_onSearchChanged);
    _scrollController.addListener(_onScroll);
    _fetchFilteredProducts();
  }

  @override
  void dispose() {
    _searchController.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  void _onSearchChanged() {
    setState(() {
      _searchQuery = _searchController.text.toLowerCase();
      _fetchFilteredProducts(isNewSearch: true);
    });
  }
  
  void _onScroll() {
    if (_scrollController.position.pixels ==
        _scrollController.position.maxScrollExtent &&
        !_isLoading &&
        _hasMoreProducts) {
      _fetchFilteredProducts();
    }
  }

  Future<void> _fetchFilteredProducts({bool isNewSearch = false}) async {
    if (_isLoading) return;

    setState(() {
      _isLoading = true;
      if (isNewSearch) {
        _currentPage = 1;
        filteredProducts.clear(); // Ensure old products are removed
        searchProduct.clear();
        _hasMoreProducts = true; // Reset load more state
      }
    });

    final productProvider = Provider.of<ProductProvider>(context, listen: false);
    final products = await productProvider.fetchProducts(
      id_categories: selectedCategories.isNotEmpty ? selectedCategories : null,
      min_price: minPrice > 0 ? minPrice : null,
      max_price: maxPrice > 0 ? maxPrice : null,
      name: _searchQuery.isNotEmpty ? _searchQuery : null,
      page: _currentPage,
      limit: 10,
    );

    setState(() {
      if (products != null && products.isNotEmpty) {
        if (isNewSearch) {
          filteredProducts = products; // Replace products for a new search
        } else {
          filteredProducts.addAll(products); // Append for pagination
        }
        searchProduct = List.from(filteredProducts); // Update displayed products
        _currentPage++;
      } else {
        _hasMoreProducts = false;
      }
      _isLoading = false;
    });
  }

  Future<void> _saveFilterValues() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('selectedCategories', jsonEncode(selectedCategories));
    await prefs.setDouble('minPrice', minPrice);
    await prefs.setDouble('maxPrice', maxPrice);
  }

  void _showFilterModal() {
    Set<String> tempCategories = Set.from(selectedCategories);
    double tempMinPrice = minPrice;
    double tempMaxPrice = maxPrice;

    final tempMinPriceController =
        TextEditingController(text: minPrice.toString());
    final tempMaxPriceController =
        TextEditingController(text: maxPrice.toString());

    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (BuildContext context) {
        final productProvider =
            Provider.of<ProductProvider>(context, listen: false);
        final categories = productProvider.categories;
        print("Categories: $categories");
        // Fetch categories from the provider

        return StatefulBuilder(
          builder: (BuildContext context, StateSetter modalSetState) {
            return Padding(
                padding: EdgeInsets.only(
                  left: 16.0,
                  right: 16.0,
                  top: 16.0,
                  bottom: MediaQuery.of(context).viewInsets.bottom,
                ),
                child: Container(
                  height: 460,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          const Text('Filter',
                              style: TextStyle(
                                  fontSize: 21, fontWeight: FontWeight.w600)),
                          TextButton(
                            onPressed: () {
                              modalSetState(() {
                                tempCategories.clear();
                                tempMinPrice = 0.0;
                                tempMaxPrice = 1000.0;
                                tempMinPriceController.text = '0.0';
                                tempMaxPriceController.text = '1000.0';
                              });
                            },
                            child: const Text('Clear',
                                style: TextStyle(
                                    color: Color(0xFFA1A1A1),
                                    fontSize: 15,
                                    fontWeight: FontWeight.w600)),
                          ),
                        ],
                      ),
                      const SizedBox(height: 24),
                      Expanded(
                        child: SingleChildScrollView(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const Text('Category',
                                style: TextStyle(
                                  color: Colors.black,
                                  fontSize: 15,
                                  fontWeight: FontWeight.w500,
                                ),),
                              const SizedBox(height: 12),
                              Wrap(
                                spacing: 10,
                                children: categories
                                    .where((category) =>
                                        category.toLowerCase() != 'home' &&
                                        category.toLowerCase() != 'offers')
                                    .map((category) {
                                  bool isSelected = tempCategories.contains(category);
                                  return FilterChip(
                                    label: Text(category, style: TextStyle(
                                      color: isSelected?Colors.white: Color(0xFF595959),
                                      fontSize: 14,
                                      fontWeight: FontWeight.w500,
                                    ),),
                                      selected: isSelected,

                                    checkmarkColor: Colors.white,
                                    selectedColor: const Color(0xFF08A657),
                                    onSelected: (bool value) {
                                      modalSetState(() {
                                        if (value) {
                                          tempCategories.add(category);
                                        } else {
                                          tempCategories.remove(category);
                                        }
                                      });
                                    },
                                  );
                                }).toList(),
                              ),
                              const SizedBox(height: 24),
                              const Text('Price (Between min and max)',
                                style: TextStyle(
                                  color: Colors.black,
                                  fontSize: 15,
                                  fontWeight: FontWeight.w500,
                                ),),
                              const SizedBox(height: 18),
                              Row(
                                children: [
                                  Expanded(
                                    child: TextField(
                                      controller: tempMinPriceController,
                                      decoration: const InputDecoration(
                                        labelText: 'Min MAD',
                                        hintText: '0.00',
                                        labelStyle:
                                            TextStyle(color: Colors.black),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Color(0xFF08A657),
                                              width: 2.0),
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Colors.black, width: 1.0),
                                        ),
                                      ),
                                      keyboardType:
                                          const TextInputType.numberWithOptions(
                                              decimal: true),
                                    ),
                                  ),
                                  const SizedBox(width: 10),
                                  Expanded(
                                    child: TextField(
                                      controller: tempMaxPriceController,
                                      decoration: const InputDecoration(
                                        labelText: 'Max MAD',
                                        hintText: '1000.00',
                                        labelStyle:
                                            TextStyle(color: Colors.black),
                                        focusedBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Color(0xFF08A657),
                                              width: 2.0),
                                        ),
                                        enabledBorder: OutlineInputBorder(
                                          borderSide: BorderSide(
                                              color: Colors.black, width: 1.0),
                                        ),
                                      ),
                                      keyboardType:
                                          const TextInputType.numberWithOptions(
                                              decimal: true),
                                    ),
                                  ),
                                ],
                              ),
                            ],
                          ),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.only(bottom: 18.0),
                        child: ElevatedButton(
                          onPressed: () {
                            double newMinPrice =
                                double.tryParse(tempMinPriceController.text) ??
                                    minPrice;
                            double newMaxPrice =
                                double.tryParse(tempMaxPriceController.text) ??
                                    maxPrice;

                            if (newMinPrice > newMaxPrice) {
                              ScaffoldMessenger.of(context).showSnackBar(
                                const SnackBar(
                                    content: Text(
                                        'Min price cannot be greater than max price')),
                              );
                              return;
                            }

                            setState(() {
                              selectedCategories = tempCategories.toList();
                              minPrice = newMinPrice;
                              maxPrice = newMaxPrice;
                            });

                            _saveFilterValues();
                            Navigator.pop(context);
                            
                            // Immediately fetch products with the new filter values
                            _fetchFilteredProducts(isNewSearch: true);
                          },
                          style: ElevatedButton.styleFrom(
                            backgroundColor: const Color(0xFF08A657),
                            minimumSize: const Size(double.infinity, 50),
                          ),
                          child: const Text('Apply filter',
                              style: TextStyle(
                                  color: Colors.white,
                                  fontSize: 16,
                                  fontWeight: FontWeight.w600)),
                        ),
                      ),
                    ],
                  ),
                ));
          },
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      resizeToAvoidBottomInset: true,
      body: Stack(
        children: [
          // Background green app bar
          ClipPath(
            clipper: CustomAppBarShapeClipper(),
            child: Container(
              color: const Color(0xFF08A657),
              height:
                  140, // This should be large enough for your custom clipper
            ),
          ),
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Search bar with back button and filter icon
                Container(
                  height: 50,
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(25),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.1),
                        spreadRadius: 1,
                        blurRadius: 5,
                      ),
                    ],
                  ),
                  child: Row(
                    children: [
                      IconButton(
                        icon: const Icon(Icons.arrow_back),
                        onPressed: () => Navigator.pop(context),
                      ),
                      Expanded(
                        child: TextField(
                          controller: _searchController,
                          decoration: const InputDecoration(
                            hintText: 'Search...',
                            border: InputBorder.none,
                          ),
                        ),
                      ),
                      const Text(
                        'Filter', // Desired text
                        style: TextStyle(
                          fontSize: 16, // Font size
                          color: Color(0xFF08A657), // Text color
                        ),
                      ),
                      IconButton(
                        icon: const Icon(Icons.filter_list,
                            color: Color(0xFF08A657)),
                        onPressed: _showFilterModal,
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 20),
                Text(
                  'Products found (${searchProduct.length})',
                  style: TextStyle(
                    color: Color(0xFFB3B3B3),
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                  ),
                ),
                const SizedBox(height: 10),

                // Product list
                Expanded(
                  child: _isLoading && searchProduct.isEmpty
                      ? Center(child: CircularProgressIndicator())
                      : ListView.builder(
                          padding: const EdgeInsets.only(top: 10.0),
                          itemCount: searchProduct.length + (_hasMoreProducts ? 1 : 0),
                          controller: _scrollController,
                          itemBuilder: (context, index) {
                            if (index < searchProduct.length) {
                              final product = searchProduct[index];
                              return Padding(
                                padding: const EdgeInsets.only(bottom: 16.0),
                                child: _buildProductCard(product, context),
                              );
                            } else {
                              // Show a loading indicator when fetching more data
                              return Center(
                                child: Padding(
                                  padding: const EdgeInsets.all(10.0),
                                  child: CircularProgressIndicator(),
                                ),
                              );
                            }
                          },
                        ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildProductCard(dynamic product, BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => ProductDetailPage(product: product),
          ),
        );
      },
      child: Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(16),
          side: const BorderSide(
            color: Color(0xffF3F3F3),
            width: 1,
          ),
        ),
        color: Colors.white,
        elevation: 0,
        child: Row(
          children: [
            ClipRRect(
              borderRadius:
                  const BorderRadius.horizontal(left: Radius.circular(16)),
              child: Image.network(
                product.imgUrl,
                fit: BoxFit.cover,
                width: 100,
                height: 100,
                errorBuilder: (context, error, stackTrace) =>
                    const Icon(Icons.error),
              ),
            ),
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      product.isNew == true ? 'New' : '',
                      style: TextStyle(
                        color: product.isNew == true
                            ? Color(0xFFE2AB00)
                            : Colors.transparent,
                        fontSize: 11,
                        fontWeight: FontWeight.w600,
                        height: 1.82,
                      ),
                    ),
                    Text(
                      product.name['en'],
                      style: const TextStyle(
                          fontSize: 18, fontWeight: FontWeight.bold),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    ),
                    const SizedBox(height: 12),
                    Text(
                      product.description['en'] ?? '',
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontSize: 14),
                    ),
                    const SizedBox(height: 20),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Text(
                          '${product.publicPrice.toStringAsFixed(2)} MAD',
                          style: const TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Color(0xFF08A657)),
                        ),
                        Container(
                          padding:
                          const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                          decoration: BoxDecoration(
                            color: product.offer == null
                                ? Colors.transparent
                                : Colors.red.shade50,
                            borderRadius: BorderRadius.circular(30),
                          ),
                          child: Text(
                            product.offer == null ? '' : '- ${product.offer} %',
                            style: TextStyle(
                              color: Color(0xFFE10C0C),
                              fontSize: 10,
                              fontWeight: FontWeight.w500,
                              height: 2,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Editor is loading...
Leave a Comment