diff --git a/lib/features/home/providers/home_order_providers.dart b/lib/features/home/providers/home_order_providers.dart index 12c551ef7..ee9e16b78 100644 --- a/lib/features/home/providers/home_order_providers.dart +++ b/lib/features/home/providers/home_order_providers.dart @@ -7,20 +7,71 @@ import 'package:mostro_mobile/shared/providers/order_repository_provider.dart'; final homeOrderTypeProvider = StateProvider((ref) => OrderType.sell); +// Filter state providers +final currencyFilterProvider = StateProvider>((ref) => []); +final paymentMethodFilterProvider = StateProvider>((ref) => []); +final ratingFilterProvider = StateProvider<({double min, double max})>((ref) => (min: 0.0, max: 5.0)); +final premiumRangeFilterProvider = StateProvider<({double min, double max})>((ref) => (min: -10.0, max: 10.0)); + final filteredOrdersProvider = Provider>((ref) { final allOrdersAsync = ref.watch(orderEventsProvider); final orderType = ref.watch(homeOrderTypeProvider); + final selectedCurrencies = ref.watch(currencyFilterProvider); + final selectedPaymentMethods = ref.watch(paymentMethodFilterProvider); + final ratingRange = ref.watch(ratingFilterProvider); + final premiumRange = ref.watch(premiumRangeFilterProvider); return allOrdersAsync.maybeWhen( data: (allOrders) { allOrders .sort((o1, o2) => o1.expirationDate.compareTo(o2.expirationDate)); - final filtered = allOrders.reversed + var filtered = allOrders.reversed .where((o) => o.orderType == orderType) - .where((o) => o.status == Status.pending) - .toList(); - return filtered; + .where((o) => o.status == Status.pending); + + // Apply currency filter + if (selectedCurrencies.isNotEmpty) { + filtered = filtered.where((o) => + o.currency != null && selectedCurrencies.contains(o.currency!) + ); + } + + // Apply payment method filter + if (selectedPaymentMethods.isNotEmpty) { + final methodsLower = selectedPaymentMethods + .where((m) => m.trim().isNotEmpty) + .map((m) => m.toLowerCase()) + .toSet(); + filtered = filtered.where((o) { + final pms = o.paymentMethods; + if (pms.isEmpty) return false; + return pms.any((pm) { + final pmLower = pm.toLowerCase(); + return methodsLower.any(pmLower.contains); + }); + }); + } + + // Apply rating filter + if (ratingRange.min > 0.0 || ratingRange.max < 5.0) { + filtered = filtered.where((o) => + o.rating != null && + o.rating!.totalRating >= ratingRange.min && + o.rating!.totalRating <= ratingRange.max + ); + } + + // Apply premium/discount filter + if (premiumRange.min > -10.0 || premiumRange.max < 10.0) { + filtered = filtered.where((o) { + if (o.premium == null || o.premium!.isEmpty) return false; + final premiumValue = double.tryParse(o.premium!) ?? 0.0; + return premiumValue >= premiumRange.min && premiumValue <= premiumRange.max; + }); + } + + return filtered.toList(); }, orElse: () => [], ); diff --git a/lib/features/home/screens/home_screen.dart b/lib/features/home/screens/home_screen.dart index 29d2b2473..b86993677 100644 --- a/lib/features/home/screens/home_screen.dart +++ b/lib/features/home/screens/home_screen.dart @@ -8,6 +8,7 @@ import 'package:mostro_mobile/features/home/widgets/order_list_item.dart'; import 'package:mostro_mobile/shared/widgets/add_order_button.dart'; import 'package:mostro_mobile/shared/widgets/bottom_nav_bar.dart'; import 'package:mostro_mobile/shared/widgets/mostro_app_bar.dart'; +import 'package:mostro_mobile/shared/widgets/order_filter.dart'; import 'package:mostro_mobile/shared/widgets/custom_drawer_overlay.dart'; import 'package:mostro_mobile/generated/l10n.dart'; @@ -196,7 +197,6 @@ class HomeScreen extends ConsumerWidget { child: Align( alignment: Alignment.centerLeft, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: AppTheme.backgroundInput, borderRadius: BorderRadius.circular(30), @@ -209,40 +209,62 @@ class HomeScreen extends ConsumerWidget { ), ], ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const HeroIcon( - HeroIcons.funnel, - style: HeroIconStyle.outline, - color: Colors.white70, - size: 18, - ), - const SizedBox(width: 8), - Text( - S.of(context)!.filter, - style: const TextStyle( - color: Colors.white70, - fontSize: 13, - fontWeight: FontWeight.w500, - letterSpacing: 0.5, - ), - ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 8), - height: 16, - width: 1, - color: Colors.white.withValues(alpha: 0.2), - ), - Text( - S.of(context)!.offersCount(filteredOrders.length.toString()), - style: const TextStyle( - color: Colors.grey, - fontSize: 12, - fontWeight: FontWeight.normal, + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(30), + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return const Dialog( + child: OrderFilter(), + ); + }, + ); + }, + borderRadius: BorderRadius.circular(30), + splashColor: AppTheme.activeColor.withValues(alpha: 0.3), + highlightColor: AppTheme.activeColor.withValues(alpha: 0.15), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const HeroIcon( + HeroIcons.funnel, + style: HeroIconStyle.outline, + color: Colors.white70, + size: 18, + ), + const SizedBox(width: 8), + Text( + S.of(context)!.filter, + style: const TextStyle( + color: Colors.white70, + fontSize: 13, + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + height: 16, + width: 1, + color: Colors.white.withValues(alpha: 0.2), + ), + Text( + S.of(context)!.offersCount(filteredOrders.length.toString()), + style: const TextStyle( + color: Colors.grey, + fontSize: 12, + fontWeight: FontWeight.normal, + ), + ), + ], ), ), - ], + ), ), ), ), diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index d6d1e2b18..c43f77c24 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -796,9 +796,20 @@ "save": "Save", + "apply": "Apply", "selectCurrency": "Select Currency", "noCurrencySelected": "No currency selected", + "@_comment_filter_section": "Filter section strings", + "rating": "Rating", + "reputation": "Reputation", + "premiumRange": "Premium/Discount", + "discount": "Discount", + "premium": "Premium", + "clear": "Clear", + "min": "Min", + "max": "Max", + "@_comment_timeout_messages": "Timeout notification messages", "orderTimeoutTaker": "You didn't respond in time. The order will be republished", "orderTimeoutMaker": "Your counterpart didn't respond in time. The order will be republished", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 21edcc01c..f42d88e0f 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -823,6 +823,17 @@ "add": "Agregar", "save": "Guardar", + "apply": "Aplicar", + + "@_comment_filter_section": "Strings de la sección de filtros", + "rating": "Calificación", + "reputation": "Reputación", + "premiumRange": "Prima/Descuento", + "discount": "Descuento", + "premium": "Prima", + "clear": "Limpiar", + "min": "Mín", + "max": "Máx", "selectCurrency": "Seleccionar Moneda", "noCurrencySelected": "Ninguna moneda seleccionada", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 70b780630..c5a5b5482 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -831,6 +831,17 @@ "add": "Aggiungi", "save": "Salva", + "apply": "Applica", + + "@_comment_filter_section": "Stringhe della sezione filtri", + "rating": "Valutazione", + "reputation": "Reputazione", + "premiumRange": "Premio/Sconto", + "discount": "Sconto", + "premium": "Premio", + "clear": "Cancella", + "min": "Min", + "max": "Max", "selectCurrency": "Seleziona Valuta", "noCurrencySelected": "Nessuna valuta selezionata", diff --git a/lib/shared/widgets/order_filter.dart b/lib/shared/widgets/order_filter.dart index 90fc5faa5..600259635 100644 --- a/lib/shared/widgets/order_filter.dart +++ b/lib/shared/widgets/order_filter.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; import 'package:mostro_mobile/core/app_theme.dart'; +import 'package:mostro_mobile/features/home/providers/home_order_providers.dart'; +import 'package:mostro_mobile/features/order/providers/payment_methods_provider.dart'; +import 'package:mostro_mobile/generated/l10n.dart'; +import 'package:mostro_mobile/shared/providers/exchange_service_provider.dart'; /// A custom multi-select field based on Autocomplete. /// It lets the user type to filter options and add selections which are shown as Chips. @@ -44,7 +49,12 @@ class MultiSelectAutocompleteState extends State { children: [ Text( widget.label, - style: const TextStyle(color: AppTheme.mostroGreen), + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), ), const SizedBox(height: 8), Autocomplete( @@ -58,6 +68,46 @@ class MultiSelectAutocompleteState extends State { .contains(textEditingValue.text.toLowerCase()) && !widget.selectedValues.contains(option)); }, + optionsViewBuilder: (context, onSelected, options) { + return Align( + alignment: Alignment.topLeft, + child: Material( + color: AppTheme.backgroundCard, + elevation: 8, + borderRadius: BorderRadius.circular(8), + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200, maxWidth: 300), + child: ListView.builder( + padding: const EdgeInsets.all(4), + shrinkWrap: true, + itemCount: options.length, + itemBuilder: (context, index) { + final option = options.elementAt(index); + return InkWell( + onTap: () => onSelected(option), + borderRadius: BorderRadius.circular(6), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + margin: const EdgeInsets.symmetric(vertical: 1), + child: Text( + option, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + }, + ), + ), + ), + ); + }, onSelected: (String selection) { final updated = List.from(widget.selectedValues) ..add(selection); @@ -70,9 +120,43 @@ class MultiSelectAutocompleteState extends State { return TextFormField( controller: textEditingController, focusNode: focusNode, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + ), decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: 'Type to add...', + filled: true, + fillColor: AppTheme.backgroundInput, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.white.withValues(alpha: 0.2), + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.white.withValues(alpha: 0.2), + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: AppTheme.textSecondary, + width: 1.5, + ), + ), + hintText: S.of(context)!.typeToAdd, + hintStyle: TextStyle( + color: AppTheme.textInactive, + fontSize: 14, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 14, + ), ), ); }, @@ -82,20 +166,69 @@ class MultiSelectAutocompleteState extends State { spacing: 8, children: widget.selectedValues.isEmpty ? [ - const Text( - 'None selected', - style: TextStyle(color: AppTheme.cream1), + Text( + S.of(context)!.noneSelected, + style: TextStyle( + color: AppTheme.textInactive, + fontSize: 13, + fontStyle: FontStyle.italic, + ), ) ] : widget.selectedValues - .map((value) => Chip( - label: Text(value), - onDeleted: () { - final updated = - List.from(widget.selectedValues) - ..remove(value); - widget.onChanged(updated); - }, + .map((value) => Container( + margin: const EdgeInsets.only(right: 6, bottom: 4), + child: Material( + color: AppTheme.backgroundInput, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppTheme.backgroundInput, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppTheme.textSecondary.withValues(alpha: 0.6), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + value, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 6), + GestureDetector( + onTap: () { + final updated = List.from(widget.selectedValues) + ..remove(value); + widget.onChanged(updated); + }, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: AppTheme.textSecondary.withValues(alpha: 0.2), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.close, + size: 14, + color: AppTheme.textSecondary, + ), + ), + ), + ], + ), + ), + ), )) .toList(), ), @@ -106,118 +239,498 @@ class MultiSelectAutocompleteState extends State { } /// The updated OrderFilter widget which uses the MultiSelectAutocomplete widgets and a slider. -class OrderFilter extends StatefulWidget { +class OrderFilter extends ConsumerStatefulWidget { const OrderFilter({super.key}); @override - OrderFilterState createState() => OrderFilterState(); + ConsumerState createState() => OrderFilterState(); } -class OrderFilterState extends State { +class OrderFilterState extends ConsumerState { List selectedFiatCurrencies = []; List selectedPaymentMethods = []; - double rating = 0.0; + double ratingMin = 0.0; + double ratingMax = 5.0; + double premiumMin = -10.0; + double premiumMax = 10.0; // Options for the multi-select fields. - final List fiatOptions = ['USD', 'EUR', 'VES']; - final List paymentMethodsOptions = [ - 'face to face', - 'bank transfer', - 'lightning' - ]; + + List _getAllPaymentMethods(Map paymentMethodsData) { + final Set allMethods = {}; + + // Add all payment methods from all currencies + for (final methods in paymentMethodsData.values) { + if (methods is List) { + allMethods.addAll(methods.cast()); + } + } + + // Remove "Other" since it's for custom input, not filtering + allMethods.remove('Other'); + + final sortedMethods = allMethods.toList()..sort(); + return sortedMethods; + } + + @override + void initState() { + super.initState(); + // Load current filter values from providers + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + + final currencies = ref.read(currencyFilterProvider); + final paymentMethods = ref.read(paymentMethodFilterProvider); + final currentRatingRange = ref.read(ratingFilterProvider); + final currentPremiumRange = ref.read(premiumRangeFilterProvider); + + setState(() { + selectedFiatCurrencies = List.from(currencies); + selectedPaymentMethods = List.from(paymentMethods); + final (min: rMin, max: rMax) = currentRatingRange; + ratingMin = rMin; + ratingMax = rMax; + premiumMin = currentPremiumRange.min; + premiumMax = currentPremiumRange.max; + }); + }); + } @override Widget build(BuildContext context) { + final currenciesAsync = ref.watch(currencyCodesProvider); + final paymentMethodsAsync = ref.watch(paymentMethodsDataProvider); + return Container( - width: 300, - padding: const EdgeInsets.all(16), + width: 320, + height: MediaQuery.of(context).size.height * 0.8, // 80% of screen height + padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: AppTheme.cream1, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: .1), - blurRadius: 5, - offset: const Offset(0, 3), - ), - ], + color: AppTheme.backgroundCard, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.white.withValues(alpha: 0.1), + width: 1, + ), + boxShadow: AppTheme.cardShadow, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, children: [ // Header with title and close button. Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( - children: const [ - HeroIcon(HeroIcons.funnel, - style: HeroIconStyle.outline, color: AppTheme.dark2), - SizedBox(width: 8), + children: [ + const HeroIcon(HeroIcons.funnel, + style: HeroIconStyle.outline, color: AppTheme.mostroGreen), + const SizedBox(width: 8), Text( - 'FILTER', - style: TextStyle( - color: AppTheme.dark2, + S.of(context)!.filter.toUpperCase(), + style: const TextStyle( + color: AppTheme.textPrimary, fontSize: 18, fontWeight: FontWeight.bold, + letterSpacing: 0.5, ), ), ], ), IconButton( - icon: const Icon(Icons.close, color: AppTheme.dark2, size: 20), + icon: const Icon(Icons.close, color: AppTheme.textSecondary, size: 22), onPressed: () { Navigator.of(context).pop(); }, + style: IconButton.styleFrom( + backgroundColor: Colors.white.withValues(alpha: 0.1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(6), + ), ), ], ), const SizedBox(height: 20), - // Fiat currencies using Autocomplete multi-select. - MultiSelectAutocomplete( - label: 'Fiat currencies', - options: fiatOptions, - selectedValues: selectedFiatCurrencies, - onChanged: (values) { - setState(() { - selectedFiatCurrencies = values; - }); - }, + // Scrollable content area + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Fiat currencies using Autocomplete multi-select. + currenciesAsync.when( + data: (currencies) => MultiSelectAutocomplete( + label: S.of(context)!.fiatCurrencies, + options: currencies.keys.toList()..sort(), + selectedValues: selectedFiatCurrencies, + onChanged: (values) { + setState(() { + selectedFiatCurrencies = values; + }); + }, + ), + loading: () => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context)!.fiatCurrencies, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + color: AppTheme.backgroundInput, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Center( + child: Text( + S.of(context)!.loadingCurrencies, + style: const TextStyle( + color: AppTheme.textInactive, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + error: (error, stack) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context)!.fiatCurrencies, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 8), + MultiSelectAutocomplete( + label: S.of(context)!.fiatCurrencies, + options: ['USD', 'EUR', 'VES'], // Fallback options + selectedValues: selectedFiatCurrencies, + onChanged: (values) { + setState(() { + selectedFiatCurrencies = values; + }); + }, + ), + ], + ), ), const SizedBox(height: 12), // Payment methods using Autocomplete multi-select. - MultiSelectAutocomplete( - label: 'Payment methods', - options: paymentMethodsOptions, - selectedValues: selectedPaymentMethods, - onChanged: (values) { - setState(() { - selectedPaymentMethods = values; - }); - }, + paymentMethodsAsync.when( + data: (paymentMethodsData) => MultiSelectAutocomplete( + label: S.of(context)!.paymentMethods, + options: _getAllPaymentMethods(paymentMethodsData), + selectedValues: selectedPaymentMethods, + onChanged: (values) { + setState(() { + selectedPaymentMethods = values; + }); + }, + ), + loading: () => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context)!.paymentMethods, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + color: AppTheme.backgroundInput, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Center( + child: Text( + S.of(context)!.loadingPaymentMethods, + style: const TextStyle( + color: AppTheme.textInactive, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + error: (error, stack) => MultiSelectAutocomplete( + label: S.of(context)!.paymentMethods, + options: ['Bank Transfer', 'Cash in person', 'PayPal', 'Zelle'], // Fallback options + selectedValues: selectedPaymentMethods, + onChanged: (values) { + setState(() { + selectedPaymentMethods = values; + }); + }, + ), ), const SizedBox(height: 12), - // Rating slider between 0 and 5. + // Premium/Discount range filter Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Rating: ${rating.toStringAsFixed(1)}", - style: const TextStyle(color: AppTheme.mostroGreen), + S.of(context)!.premiumRange, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Text( + "${S.of(context)!.discount}: ${premiumMin.toInt()}%", + style: const TextStyle( + color: AppTheme.sellColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + Text( + "${S.of(context)!.premium}: ${premiumMax.toInt()}%", + style: const TextStyle( + color: AppTheme.buyColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: AppTheme.textSecondary, + inactiveTrackColor: AppTheme.backgroundInput, + thumbColor: AppTheme.textSecondary, + overlayColor: AppTheme.textSecondary.withValues(alpha: 0.2), + valueIndicatorColor: AppTheme.textSecondary, + valueIndicatorTextStyle: const TextStyle( + color: AppTheme.backgroundDark, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + trackHeight: 4, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 8, + ), + ), + child: RangeSlider( + values: RangeValues(premiumMin, premiumMax), + min: -10.0, + max: 10.0, + divisions: 20, + labels: RangeLabels( + "${premiumMin.toInt()}%", + "${premiumMax.toInt()}%" + ), + onChanged: (values) { + setState(() { + premiumMin = values.start; + premiumMax = values.end; + }); + }, + ), + ), + ], + ), + const SizedBox(height: 12), + // Rating range slider between 0 and 5. + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context)!.reputation, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Text( + "${S.of(context)!.min}: ${ratingMin.toInt()}", + style: const TextStyle( + color: AppTheme.sellColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + Text( + "${S.of(context)!.max}: ${ratingMax.toInt()}", + style: const TextStyle( + color: AppTheme.buyColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: AppTheme.textSecondary, + inactiveTrackColor: AppTheme.backgroundInput, + thumbColor: AppTheme.textSecondary, + overlayColor: AppTheme.textSecondary.withValues(alpha: 0.2), + valueIndicatorColor: AppTheme.textSecondary, + valueIndicatorTextStyle: const TextStyle( + color: AppTheme.backgroundDark, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + trackHeight: 4, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 8, + ), + ), + child: RangeSlider( + values: RangeValues(ratingMin, ratingMax), + min: 0.0, + max: 5.0, + divisions: 5, + labels: RangeLabels( + ratingMin.toInt().toString(), + ratingMax.toInt().toString() + ), + onChanged: (values) { + setState(() { + ratingMin = values.start; + ratingMax = values.end; + }); + }, + ), + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: 20), + // Apply and Clear buttons (always visible at bottom) + Row( + children: [ + Flexible( + flex: 1, + child: SizedBox( + width: double.infinity, + height: 50, + child: OutlinedButton( + onPressed: () { + // Clear all filters + setState(() { + selectedFiatCurrencies.clear(); + selectedPaymentMethods.clear(); + ratingMin = 0.0; + ratingMax = 5.0; + premiumMin = -10.0; + premiumMax = 10.0; + }); + + ref.read(currencyFilterProvider.notifier).state = []; + ref.read(paymentMethodFilterProvider.notifier).state = []; + ref.read(ratingFilterProvider.notifier).state = (min: 0.0, max: 5.0); + ref.read(premiumRangeFilterProvider.notifier).state = (min: -10.0, max: 10.0); + + Navigator.of(context).pop(); + }, + style: OutlinedButton.styleFrom( + foregroundColor: AppTheme.textSecondary, + side: BorderSide( + color: AppTheme.textSecondary.withValues(alpha: 0.3), + width: 1, + ), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text( + S.of(context)!.clear.toUpperCase(), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.8, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + Flexible( + flex: 1, + child: SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: () { + // Apply filters to providers + ref.read(currencyFilterProvider.notifier).state = selectedFiatCurrencies; + ref.read(paymentMethodFilterProvider.notifier).state = selectedPaymentMethods; + ref.read(ratingFilterProvider.notifier).state = (min: ratingMin, max: ratingMax); + ref.read(premiumRangeFilterProvider.notifier).state = (min: premiumMin, max: premiumMax); + + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.mostroGreen, + foregroundColor: AppTheme.backgroundDark, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 0, + shadowColor: Colors.transparent, + ), + child: Text( + S.of(context)!.apply.toUpperCase(), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.8, + ), + ), + ), + ), ), - Slider( - value: rating, - min: 0, - max: 5, - divisions: 5, - label: rating.toStringAsFixed(1), - onChanged: (val) { - setState(() { - rating = val; - }); - }, - ) ], ), ],