diff --git a/android/app/build.gradle b/android/app/build.gradle index 996fcf6b..cd8a496a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ plugins { android { namespace = "network.mostro.app" - compileSdk = 35 // flutter.compileSdkVersion - ndkVersion = "26.3.11579264" //flutter.ndkVersion + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { coreLibraryDesugaringEnabled true @@ -26,7 +26,7 @@ android { applicationId = "network.mostro.app" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 23 // flutter.minSdkVersion + minSdk = flutter.minSdkVersion // flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/l10n.yaml b/l10n.yaml index baa8e2be..3f5f9f96 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -2,5 +2,4 @@ arb-dir: lib/l10n template-arb-file: intl_en.arb output-dir: lib/generated output-localization-file: l10n.dart -output-class: S -synthetic-package: false \ No newline at end of file +output-class: S \ No newline at end of file diff --git a/lib/data/models/mostro_message.dart b/lib/data/models/mostro_message.dart index 93fa9d9d..673a5ee6 100644 --- a/lib/data/models/mostro_message.dart +++ b/lib/data/models/mostro_message.dart @@ -175,7 +175,7 @@ class MostroMessage { NostrKeyPairs? masterKey, int? keyIndex, }) async { - this.tradeIndex = keyIndex; + tradeIndex = keyIndex; final content = serialize(keyPair: masterKey != null ? tradeKey : null); final keySet = masterKey ?? tradeKey; diff --git a/lib/data/repositories/mostro_storage.dart b/lib/data/repositories/mostro_storage.dart index 1cbfd432..12b2b46a 100644 --- a/lib/data/repositories/mostro_storage.dart +++ b/lib/data/repositories/mostro_storage.dart @@ -17,9 +17,7 @@ class MostroStorage extends BaseStorage { if (await hasItem(id)) return; // Add metadata for easier querying final Map dbMap = message.toJson(); - if (message.timestamp == null) { - message.timestamp = DateTime.now().millisecondsSinceEpoch; - } + message.timestamp ??= DateTime.now().millisecondsSinceEpoch; dbMap['timestamp'] = message.timestamp; await store.record(id).put(db, dbMap); diff --git a/lib/features/auth/screens/register_screen.dart b/lib/features/auth/screens/register_screen.dart index 219cccc6..0dbe794d 100644 --- a/lib/features/auth/screens/register_screen.dart +++ b/lib/features/auth/screens/register_screen.dart @@ -182,7 +182,7 @@ class RegisterScreen extends HookConsumerWidget { ref.read(useBiometricsProvider.notifier).state = value; }, - activeColor: Colors.green, + activeThumbColor: AppTheme.activeColor, ) : const SizedBox.shrink(); }), diff --git a/lib/features/key_manager/key_management_screen.dart b/lib/features/key_manager/key_management_screen.dart index 09495de1..e863c8a3 100644 --- a/lib/features/key_manager/key_management_screen.dart +++ b/lib/features/key_manager/key_management_screen.dart @@ -57,8 +57,8 @@ class _KeyManagementScreenState extends ConsumerState { } Future _generateNewMasterKey() async { - final sessionNotifer = ref.read(sessionNotifierProvider.notifier); - await sessionNotifer.reset(); + final sessionNotifier = ref.read(sessionNotifierProvider.notifier); + await sessionNotifier.reset(); final mostroStorage = ref.read(mostroStorageProvider); await mostroStorage.deleteAll(); @@ -396,7 +396,7 @@ class _KeyManagementScreenState extends ConsumerState { .watch(settingsProvider.notifier) .updatePrivacyMode(value); }, - activeColor: AppTheme.activeColor, + activeThumbColor: AppTheme.activeColor, inactiveThumbColor: AppTheme.textSecondary, inactiveTrackColor: AppTheme.backgroundInactive, ), @@ -672,7 +672,8 @@ class _KeyManagementScreenState extends ConsumerState { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), child: Text( S.of(context)!.continueButton, diff --git a/lib/features/order/screens/add_order_screen.dart b/lib/features/order/screens/add_order_screen.dart index 4c875653..43d69b7b 100644 --- a/lib/features/order/screens/add_order_screen.dart +++ b/lib/features/order/screens/add_order_screen.dart @@ -15,6 +15,8 @@ import 'package:mostro_mobile/features/order/widgets/premium_section.dart'; import 'package:mostro_mobile/features/order/widgets/price_type_section.dart'; import 'package:mostro_mobile/features/order/widgets/form_section.dart'; import 'package:mostro_mobile/shared/providers/exchange_service_provider.dart'; +import 'package:mostro_mobile/shared/providers/order_repository_provider.dart'; +import 'package:mostro_mobile/features/mostro/mostro_instance.dart'; import 'package:mostro_mobile/features/settings/settings_provider.dart'; import 'package:uuid/uuid.dart'; import 'package:mostro_mobile/generated/l10n.dart'; @@ -40,6 +42,7 @@ class _AddOrderScreenState extends ConsumerState { int? _minFiatAmount; int? _maxFiatAmount; + String? _validationError; List _selectedPaymentMethods = []; bool _showCustomPaymentMethod = false; @@ -63,10 +66,12 @@ class _AddOrderScreenState extends ConsumerState { // Reset selectedFiatCodeProvider to default from settings for each new order final settings = ref.read(settingsProvider); - ref.read(selectedFiatCodeProvider.notifier).state = settings.defaultFiatCode; - + ref.read(selectedFiatCodeProvider.notifier).state = + settings.defaultFiatCode; + // Pre-populate lightning address from settings if available - if (settings.defaultLightningAddress != null && settings.defaultLightningAddress!.isNotEmpty) { + if (settings.defaultLightningAddress != null && + settings.defaultLightningAddress!.isNotEmpty) { _lightningAddressController.text = settings.defaultLightningAddress!; } }); @@ -85,9 +90,99 @@ class _AddOrderScreenState extends ConsumerState { setState(() { _minFiatAmount = minAmount; _maxFiatAmount = maxAmount; + + // Use comprehensive validation to check all error conditions + _validationError = _validateAllAmounts(); }); } + /// Converts fiat amount to sats using exchange rate + /// Formula: fiatAmount / exchangeRate * 100000000 (sats per BTC) + int _calculateSatsFromFiat(double fiatAmount, double exchangeRate) { + if (exchangeRate <= 0) return 0; + return (fiatAmount / exchangeRate * 100000000).round(); + } + + /// Validates if sats amount is within mostro instance allowed range + /// Returns error message if validation fails or data is missing + String? _validateSatsRange(double fiatAmount) { + // Ensure fiat code is selected + final selectedFiatCode = ref.read(selectedFiatCodeProvider); + if (selectedFiatCode == null || selectedFiatCode.isEmpty) { + return S.of(context)!.pleaseSelectCurrency; + } + + // Get exchange rate - return error if not available + final exchangeRateAsync = ref.read(exchangeRateProvider(selectedFiatCode)); + final exchangeRate = exchangeRateAsync.asData?.value; + if (exchangeRate == null) { + // Check if it's loading or error + if (exchangeRateAsync.isLoading) { + return S.of(context)!.exchangeRateNotAvailable; + } else if (exchangeRateAsync.hasError) { + return S.of(context)!.exchangeRateNotAvailable; + } + // Fallback for any other case where rate is null + return S.of(context)!.exchangeRateNotAvailable; + } + + // Get mostro instance limits - return error if not available + final mostroInstance = ref.read(orderRepositoryProvider).mostroInstance; + if (mostroInstance == null) { + return S.of(context)!.mostroInstanceNotAvailable; + } + + // Calculate sats equivalent + final satsAmount = _calculateSatsFromFiat(fiatAmount, exchangeRate); + final minAllowed = mostroInstance.minOrderAmount; + final maxAllowed = mostroInstance.maxOrderAmount; + + // Debug logging + debugPrint( + 'Validation: fiat=$fiatAmount, rate=$exchangeRate, sats=$satsAmount, min=$minAllowed, max=$maxAllowed'); + + // Check if sats amount is outside range + if (satsAmount < minAllowed) { + return S.of(context)!.fiatAmountTooLow( + minAllowed.toString(), + maxAllowed.toString(), + ); + } else if (satsAmount > maxAllowed) { + return S.of(context)!.fiatAmountTooHigh( + minAllowed.toString(), + maxAllowed.toString(), + ); + } + + // Validation passed + return null; + } + + /// Comprehensive validation for all fiat amount inputs + /// Returns error message if validation fails, null if all validations pass + String? _validateAllAmounts() { + // Check min/max relationship for range orders + if (_minFiatAmount != null && _maxFiatAmount != null) { + if (_maxFiatAmount! <= _minFiatAmount!) { + return S.of(context)!.maxMustBeGreaterThanMin; + } + } + + // Check sats range validation for min amount + if (_minFiatAmount != null) { + final minError = _validateSatsRange(_minFiatAmount!.toDouble()); + if (minError != null) return minError; + } + + // Check sats range validation for max amount + if (_maxFiatAmount != null) { + final maxError = _validateSatsRange(_maxFiatAmount!.toDouble()); + if (maxError != null) return maxError; + } + + return null; // All validations passed + } + @override Widget build(BuildContext context) { return Scaffold( @@ -130,13 +225,18 @@ class _AddOrderScreenState extends ConsumerState { CurrencySection( orderType: _orderType, onCurrencySelected: () { - setState(() {}); + setState(() { + // Re-validate after currency change since rates/limits differ + _validationError = _validateAllAmounts(); + }); }, ), const SizedBox(height: 16), AmountSection( orderType: _orderType, onAmountChanged: _onAmountChanged, + validateSatsRange: _validateSatsRange, + validationError: _validationError, ), const SizedBox(height: 16), PaymentMethodsSection( @@ -228,7 +328,7 @@ class _AddOrderScreenState extends ConsumerState { child: ActionButtons( key: const Key('addOrderButtons'), onCancel: () => context.pop(), - onSubmit: _submitOrder, + onSubmit: _getSubmitCallback(), currentRequestId: _currentRequestId, ), ), @@ -244,6 +344,31 @@ class _AddOrderScreenState extends ConsumerState { ); } + /// Returns submit callback only when form is valid, null otherwise + /// This prevents button loading state when validation errors exist + VoidCallback? _getSubmitCallback() { + // Don't allow submission if validation errors exist + if (_validationError != null) { + return null; // Disables button, prevents loading state + } + + // Check other basic conditions that would prevent submission + final selectedFiatCode = ref.read(selectedFiatCodeProvider); + if (selectedFiatCode == null || selectedFiatCode.isEmpty) { + return null; + } + + if (_selectedPaymentMethods.isEmpty) { + return null; + } + + if (_validationError != null) { + return null; + } + + return _submitOrder; // Form is valid - allow submission + } + void _submitOrder() { if (_formKey.currentState?.validate() ?? false) { final selectedFiatCode = ref.read(selectedFiatCodeProvider); @@ -271,6 +396,32 @@ class _AddOrderScreenState extends ConsumerState { return; } + // Enhanced validation: check sats range for both min and max amounts + // This is a critical final validation before submission + if (_validationError != null) { + debugPrint( + 'Submission blocked: Validation error present: $_validationError'); + // Validation error is already displayed inline, just prevent submission + return; + } + + // Additional safety check: ensure we have valid data for submission + final exchangeRateAsync = ref.read(exchangeRateProvider(fiatCode)); + final mostroInstance = ref.read(orderRepositoryProvider).mostroInstance; + + if (!exchangeRateAsync.hasValue || mostroInstance == null) { + debugPrint( + 'Submission blocked: Required data not available - Exchange rate: ${exchangeRateAsync.hasValue}, Mostro instance: ${mostroInstance != null}'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(S.of(context)!.exchangeRateNotAvailable), + duration: const Duration(seconds: 3), + backgroundColor: Colors.orange.withValues(alpha: 0.8), + ), + ); + return; + } + try { final uuid = const Uuid(); final tempOrderId = uuid.v4(); @@ -290,32 +441,29 @@ class _AddOrderScreenState extends ConsumerState { final satsAmount = int.tryParse(_satsAmountController.text) ?? 0; - List paymentMethods = List.from(_selectedPaymentMethods); if (_showCustomPaymentMethod && _customPaymentMethodController.text.isNotEmpty) { - // Remove localized "Other" (case-insensitive, trimmed) from the list final localizedOther = S.of(context)!.other.trim().toLowerCase(); paymentMethods.removeWhere( (method) => method.trim().toLowerCase() == localizedOther, ); - + String sanitizedPaymentMethod = _customPaymentMethodController.text; - + final problematicChars = RegExp(r'[,"\\\[\]{}]'); sanitizedPaymentMethod = sanitizedPaymentMethod .replaceAll(problematicChars, ' ') .replaceAll(RegExp(r'\s+'), ' ') .trim(); - + if (sanitizedPaymentMethod.isNotEmpty) { paymentMethods.add(sanitizedPaymentMethod); } } - final buyerInvoice = _orderType == OrderType.buy && _lightningAddressController.text.isNotEmpty ? _lightningAddressController.text diff --git a/lib/features/order/widgets/action_buttons.dart b/lib/features/order/widgets/action_buttons.dart index 27302a26..163998d3 100644 --- a/lib/features/order/widgets/action_buttons.dart +++ b/lib/features/order/widgets/action_buttons.dart @@ -6,13 +6,13 @@ import 'package:mostro_mobile/generated/l10n.dart'; class ActionButtons extends StatelessWidget { final VoidCallback onCancel; - final VoidCallback onSubmit; + final VoidCallback? onSubmit; final int? currentRequestId; const ActionButtons({ super.key, required this.onCancel, - required this.onSubmit, + this.onSubmit, required this.currentRequestId, }); @@ -50,9 +50,13 @@ class ActionButtons extends StatelessWidget { action: nostr_action.Action.newOrder, onPressed: onSubmit, timeout: const Duration(seconds: 5), - showSuccessIndicator: true, - backgroundColor: AppTheme.purpleButton, - foregroundColor: Colors.white, + showSuccessIndicator: + onSubmit != null, // Only show success indicator when enabled + backgroundColor: onSubmit != null + ? AppTheme.purpleButton + : AppTheme.backgroundInactive, + foregroundColor: + onSubmit != null ? Colors.white : AppTheme.textInactive, ), ), ), diff --git a/lib/features/order/widgets/amount_section.dart b/lib/features/order/widgets/amount_section.dart index 285efba8..8fc810dd 100644 --- a/lib/features/order/widgets/amount_section.dart +++ b/lib/features/order/widgets/amount_section.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:mostro_mobile/core/app_theme.dart'; import 'package:mostro_mobile/data/models/enums/order_type.dart'; import 'package:mostro_mobile/features/order/widgets/form_section.dart'; import 'package:mostro_mobile/generated/l10n.dart'; @@ -7,11 +8,15 @@ import 'package:mostro_mobile/generated/l10n.dart'; class AmountSection extends StatefulWidget { final OrderType orderType; final Function(int? minAmount, int? maxAmount) onAmountChanged; + final String? Function(double)? validateSatsRange; + final String? validationError; const AmountSection({ super.key, required this.orderType, required this.onAmountChanged, + this.validateSatsRange, + this.validationError, }); @override @@ -119,31 +124,57 @@ class _AmountSectionState extends State { } String? _validateMinAmount(String? value) { - if (value == null || value.isEmpty) { - return S.of(context)!.pleaseEnterAmount; - } - if (int.tryParse(value) == null) { - return S.of(context)!.pleaseEnterValidAmount; + // Only validate format - all error messages handled by validationError widget + if (value != null && value.isNotEmpty && int.tryParse(value) == null) { + return ''; // Return empty string to trigger form validation but not show message } return null; } String? _validateMaxAmount(String? value) { - if (value == null || value.isEmpty) { - return null; // Max amount is optional - } - if (int.tryParse(value) == null) { - return S.of(context)!.pleaseEnterValidAmount; - } - - final minAmount = int.tryParse(_minAmountController.text); - final maxAmount = int.tryParse(value); - if (minAmount != null && maxAmount != null && maxAmount <= minAmount) { - return S.of(context)!.maxMustBeGreaterThanMin; + // Only validate format - all error messages handled by validationError widget + if (value != null && value.isNotEmpty && int.tryParse(value) == null) { + return ''; // Return empty string to trigger form validation but not show message } return null; } + Widget _buildValidationMessage() { + if (widget.validationError == null) return const SizedBox.shrink(); + + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppTheme.statusError.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppTheme.statusError.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.warning_amber_rounded, + color: AppTheme.statusError, + size: 18, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + widget.validationError!, + style: const TextStyle( + color: AppTheme.statusError, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return FormSection( @@ -217,6 +248,12 @@ class _AmountSectionState extends State { ), ), ], + + // Validation message (shown inside card when there's an error) + if (widget.validationError != null) ...[ + const SizedBox(height: 12), + _buildValidationMessage(), + ], ], ), ); diff --git a/lib/features/order/widgets/price_type_section.dart b/lib/features/order/widgets/price_type_section.dart index 371dc8e9..4dcdf00f 100644 --- a/lib/features/order/widgets/price_type_section.dart +++ b/lib/features/order/widgets/price_type_section.dart @@ -53,7 +53,7 @@ class PriceTypeSection extends StatelessWidget { Switch( key: const Key('fixedSwitch'), value: isMarketRate, - activeColor: + activeThumbColor: AppTheme.purpleAccent, // Keep the purple accent color onChanged: onToggle, ), diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0b8be7b0..f962edb7 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -369,6 +369,34 @@ "enterAmountHint": "Enter amount", "pleaseEnterAmount": "Please enter an amount", "pleaseEnterValidAmount": "Please enter a valid amount", + "fiatAmountTooHigh": "The fiat amount is too high, please add a new fiat amount, the allowed range is between {minAmount} and {maxAmount} sats", + "@fiatAmountTooHigh": { + "placeholders": { + "minAmount": { + "type": "String", + "description": "The minimum allowed sats amount" + }, + "maxAmount": { + "type": "String", + "description": "The maximum allowed sats amount" + } + } + }, + "fiatAmountTooLow": "The fiat amount is too low, please add a new fiat amount, the allowed range is between {minAmount} and {maxAmount} sats", + "@fiatAmountTooLow": { + "placeholders": { + "minAmount": { + "type": "String", + "description": "The minimum allowed sats amount" + }, + "maxAmount": { + "type": "String", + "description": "The maximum allowed sats amount" + } + } + }, + "exchangeRateNotAvailable": "Exchange rate not available, please wait or refresh", + "mostroInstanceNotAvailable": "Mostro instance data not available, please wait", "enterAmountYouWantToReceive": "Enter amount you want to receive", "enterAmountYouWantToSend": "Enter amount you want to send", "creatingRangeOrder": "Creating a range order (you can receive between min and max amounts)", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index f69dd0bc..5fb511a6 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -325,6 +325,34 @@ "enterAmountHint": "Ingresa cantidad", "pleaseEnterAmount": "Por favor ingresa una cantidad", "pleaseEnterValidAmount": "Por favor ingresa una cantidad válida", + "fiatAmountTooHigh": "La cantidad fiat es demasiado alta, por favor ingresa una nueva cantidad fiat, el rango permitido está entre {minAmount} y {maxAmount} sats", + "@fiatAmountTooHigh": { + "placeholders": { + "minAmount": { + "type": "String", + "description": "The minimum allowed sats amount" + }, + "maxAmount": { + "type": "String", + "description": "The maximum allowed sats amount" + } + } + }, + "fiatAmountTooLow": "La cantidad fiat es demasiado baja, por favor ingresa una nueva cantidad fiat, el rango permitido está entre {minAmount} y {maxAmount} sats", + "@fiatAmountTooLow": { + "placeholders": { + "minAmount": { + "type": "String", + "description": "The minimum allowed sats amount" + }, + "maxAmount": { + "type": "String", + "description": "The maximum allowed sats amount" + } + } + }, + "exchangeRateNotAvailable": "Tasa de cambio no disponible, por favor espera o actualiza", + "mostroInstanceNotAvailable": "Datos de instancia Mostro no disponibles, por favor espera", "enterAmountYouWantToReceive": "Ingresa la cantidad que quieres recibir", "enterAmountYouWantToSend": "Ingresa la cantidad que quieres enviar", "creatingRangeOrder": "Creando orden de rango (puedes recibir entre cantidades mínima y máxima)", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 57a3bc21..f1822791 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -330,6 +330,34 @@ "enterAmountHint": "Inserisci importo", "pleaseEnterAmount": "Per favore inserisci un importo", "pleaseEnterValidAmount": "Per favore inserisci un importo valido", + "fiatAmountTooHigh": "L'importo fiat è troppo alto, per favore inserisci un nuovo importo fiat, l'intervallo consentito è tra {minAmount} e {maxAmount} sats", + "@fiatAmountTooHigh": { + "placeholders": { + "minAmount": { + "type": "String", + "description": "The minimum allowed sats amount" + }, + "maxAmount": { + "type": "String", + "description": "The maximum allowed sats amount" + } + } + }, + "fiatAmountTooLow": "L'importo fiat è troppo basso, per favore inserisci un nuovo importo fiat, l'intervallo consentito è tra {minAmount} e {maxAmount} sats", + "@fiatAmountTooLow": { + "placeholders": { + "minAmount": { + "type": "String", + "description": "The minimum allowed sats amount" + }, + "maxAmount": { + "type": "String", + "description": "The maximum allowed sats amount" + } + } + }, + "exchangeRateNotAvailable": "Tasso di cambio non disponibile, per favore attendi o aggiorna", + "mostroInstanceNotAvailable": "Dati dell'istanza Mostro non disponibili, per favore attendi", "enterAmountYouWantToReceive": "Inserisci l'importo che vuoi ricevere", "enterAmountYouWantToSend": "Inserisci l'importo che vuoi inviare", "creatingRangeOrder": "Creazione ordine intervallo (puoi ricevere tra importi minimo e massimo)", diff --git a/lib/shared/widgets/mostro_reactive_button.dart b/lib/shared/widgets/mostro_reactive_button.dart index 6097fde3..80c31065 100644 --- a/lib/shared/widgets/mostro_reactive_button.dart +++ b/lib/shared/widgets/mostro_reactive_button.dart @@ -12,15 +12,15 @@ enum ButtonStyleType { raised, outlined, text } /// Controller for managing MostroReactiveButton state externally class MostroReactiveButtonController { _MostroReactiveButtonState? _state; - + void _attach(_MostroReactiveButtonState state) { _state = state; } - + void _detach() { _state = null; } - + void resetLoading() { _state?.resetLoading(); } @@ -29,7 +29,7 @@ class MostroReactiveButtonController { class MostroReactiveButton extends ConsumerStatefulWidget { final String label; final ButtonStyleType buttonStyle; - final VoidCallback onPressed; + final VoidCallback? onPressed; final String orderId; final actions.Action action; final Duration timeout; @@ -44,7 +44,7 @@ class MostroReactiveButton extends ConsumerStatefulWidget { super.key, required this.label, required this.buttonStyle, - required this.onPressed, + this.onPressed, required this.orderId, required this.action, this.timeout = const Duration(seconds: 5), @@ -87,11 +87,13 @@ class _MostroReactiveButtonState extends ConsumerState { } void _startOperation() { + if (widget.onPressed == null) return; + setState(() { _loading = true; _showSuccess = false; }); - widget.onPressed(); + widget.onPressed!(); _timeoutTimer?.cancel(); _timeoutTimer = Timer(widget.timeout, _handleTimeout); } @@ -155,23 +157,28 @@ class _MostroReactiveButtonState extends ConsumerState { switch (widget.buttonStyle) { case ButtonStyleType.raised: button = ElevatedButton( - onPressed: _loading ? null : _startOperation, - style: (widget.backgroundColor != null || widget.foregroundColor != null) - ? AppTheme.theme.elevatedButtonTheme.style?.copyWith( - backgroundColor: widget.backgroundColor != null - ? WidgetStateProperty.resolveWith((_) => widget.backgroundColor!) - : null, - foregroundColor: widget.foregroundColor != null - ? WidgetStateProperty.resolveWith((_) => widget.foregroundColor!) - : null, - ) - : AppTheme.theme.elevatedButtonTheme.style, + onPressed: + (_loading || widget.onPressed == null) ? null : _startOperation, + style: + (widget.backgroundColor != null || widget.foregroundColor != null) + ? AppTheme.theme.elevatedButtonTheme.style?.copyWith( + backgroundColor: widget.backgroundColor != null + ? WidgetStateProperty.resolveWith( + (_) => widget.backgroundColor!) + : null, + foregroundColor: widget.foregroundColor != null + ? WidgetStateProperty.resolveWith( + (_) => widget.foregroundColor!) + : null, + ) + : AppTheme.theme.elevatedButtonTheme.style, child: childWidget, ); break; case ButtonStyleType.outlined: button = OutlinedButton( - onPressed: _loading ? null : _startOperation, + onPressed: + (_loading || widget.onPressed == null) ? null : _startOperation, style: widget.backgroundColor != null ? AppTheme.theme.outlinedButtonTheme.style?.copyWith( backgroundColor: WidgetStateProperty.resolveWith( @@ -184,7 +191,8 @@ class _MostroReactiveButtonState extends ConsumerState { break; case ButtonStyleType.text: button = TextButton( - onPressed: _loading ? null : _startOperation, + onPressed: + (_loading || widget.onPressed == null) ? null : _startOperation, style: widget.backgroundColor != null ? AppTheme.theme.textButtonTheme.style?.copyWith( backgroundColor: WidgetStateProperty.resolveWith( diff --git a/lib/shared/widgets/privacy_switch_widget.dart b/lib/shared/widgets/privacy_switch_widget.dart deleted file mode 100644 index f1541ab2..00000000 --- a/lib/shared/widgets/privacy_switch_widget.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:mostro_mobile/core/app_theme.dart'; - -class PrivacySwitch extends StatefulWidget { - final bool initialValue; - final ValueChanged onChanged; - - const PrivacySwitch({ - super.key, - this.initialValue = false, - required this.onChanged, - }); - - @override - State createState() => _PrivacySwitchState(); -} - -class _PrivacySwitchState extends State { - late bool _isFullPrivacy; - - @override - void initState() { - super.initState(); - _isFullPrivacy = widget.initialValue; - } - - @override - Widget build(BuildContext context) { - return Row( - children: [ - HeroIcon( - _isFullPrivacy ? HeroIcons.eyeSlash : HeroIcons.eye, - style: HeroIconStyle.outline, - color: AppTheme.cream1, - size: 24, - ), - const SizedBox(width: 8), - // The Switch control. - Switch( - value: _isFullPrivacy, - onChanged: (value) { - setState(() { - _isFullPrivacy = value; - }); - widget.onChanged(value); - }, - activeColor: AppTheme.mostroGreen, - inactiveThumbColor: Colors.grey, - ), - const SizedBox(width: 8), - // A text label that changes based on the switch value. - Text( - _isFullPrivacy ? 'Full Privacy' : 'Normal Privacy', - style: const TextStyle(color: AppTheme.cream1), - ), - ], - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index 7b9f004e..428fbca1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -840,26 +840,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1462,26 +1462,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.25.15" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.8" + version: "0.6.11" timeago: dependency: "direct main" description: @@ -1614,10 +1614,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: