Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions lib/data/models/payment_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ class PaymentRequest implements Payload {

values.add(order?.toJson());
values.add(lnInvoice);

if (amount != null) {
values.add(amount);
}
values.add(amount); // Always add amount, even if null

final result = {typeKey: values};

Expand Down
66 changes: 65 additions & 1 deletion lib/features/order/notfiers/abstract_mostro_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:mostro_mobile/shared/providers.dart';
import 'package:mostro_mobile/features/chat/providers/chat_room_providers.dart';
import 'package:mostro_mobile/features/notifications/providers/notifications_provider.dart';
import 'package:mostro_mobile/features/notifications/utils/notification_data_extractor.dart';
import 'package:mostro_mobile/features/settings/settings_provider.dart';
import 'package:logger/logger.dart';

class AbstractMostroNotifier extends StateNotifier<OrderState> {
Expand Down Expand Up @@ -188,7 +189,7 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
case Action.addInvoice:
final sessionNotifier = ref.read(sessionNotifierProvider.notifier);
sessionNotifier.saveSession(session);
navProvider.go('/add_invoice/$orderId');
await _handleAddInvoiceWithAutoLightningAddress(event);
break;

case Action.holdInvoicePaymentAccepted:
Expand Down Expand Up @@ -511,6 +512,69 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
}
}

/// Handles add-invoice action with automatic Lightning address sending if available
Future<void> _handleAddInvoiceWithAutoLightningAddress(MostroMessage event) async {
// Check if this add-invoice comes after a payment-failed
// If status is paymentFailed, don't auto-send Lightning address - use manual input
if (state.status == Status.paymentFailed) {
logger.i('add-invoice after payment-failed detected - using manual input instead of auto Lightning address');
_navigateToManualInvoiceInput();
return;
}

final settings = ref.read(settingsProvider);
final lightningAddress = settings.defaultLightningAddress?.trim();

if (lightningAddress != null && lightningAddress.isNotEmpty && _isValidLightningAddress(lightningAddress)) {
logger.i('Auto-sending Lightning address for add-invoice: $lightningAddress');

try {
// For Lightning addresses, amount should always be null
// because the Lightning address handles the amount automatically
const int? amount = null;
logger.i('Sending Lightning address with amount: null (Lightning address handles amount)');

// Send Lightning address automatically
final mostroService = ref.read(mostroServiceProvider);
await mostroService.sendInvoice(orderId, lightningAddress, amount);

// Check if still mounted after async operation
if (!mounted) return;

// Show feedback to user
final notificationNotifier = ref.read(notificationActionsProvider.notifier);
notificationNotifier.showCustomMessage('lightningAddressUsed');

logger.i('Lightning address sent successfully for order: $orderId');
} catch (e) {
logger.e('Failed to send Lightning address automatically: $e');
// Check if still mounted after async operation
if (!mounted) return;
// Fallback to manual input if auto-send fails
_navigateToManualInvoiceInput();
}
} else {
// No Lightning address or invalid format - use manual input
_navigateToManualInvoiceInput();
}
}

/// Validates Lightning address format (user@domain.tld)
bool _isValidLightningAddress(String address) {
// Lightning address format: user@domain.tld
// More robust validation with character constraints
final lnAddressRegex = RegExp(
r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
);
return lnAddressRegex.hasMatch(address);
}

/// Navigate to manual invoice input screen
void _navigateToManualInvoiceInput() {
final navProvider = ref.read(navigationProvider.notifier);
navProvider.go('/add_invoice/$orderId');
}

@override
void dispose() {
subscription?.close();
Expand Down
5 changes: 4 additions & 1 deletion lib/features/settings/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Settings {
String? defaultFiatCode,
String? selectedLanguage,
String? defaultLightningAddress,
bool clearDefaultLightningAddress = false,
List<String>? blacklistedRelays,
List<Map<String, dynamic>>? userRelays,
}) {
Expand All @@ -35,7 +36,9 @@ class Settings {
mostroPublicKey: mostroPublicKey ?? this.mostroPublicKey,
defaultFiatCode: defaultFiatCode ?? this.defaultFiatCode,
selectedLanguage: selectedLanguage ?? this.selectedLanguage,
defaultLightningAddress: defaultLightningAddress ?? this.defaultLightningAddress,
defaultLightningAddress: clearDefaultLightningAddress
? null
: (defaultLightningAddress ?? this.defaultLightningAddress),
blacklistedRelays: blacklistedRelays ?? this.blacklistedRelays,
userRelays: userRelays ?? this.userRelays,
);
Expand Down
8 changes: 7 additions & 1 deletion lib/features/settings/settings_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ class SettingsNotifier extends StateNotifier<Settings> {
}

Future<void> updateDefaultLightningAddress(String? newValue) async {
state = state.copyWith(defaultLightningAddress: newValue);
if (newValue == null || newValue.trim().isEmpty) {
// Clear the Lightning address
state = state.copyWith(clearDefaultLightningAddress: true);
} else {
// Set the Lightning address
state = state.copyWith(defaultLightningAddress: newValue.trim());
}
await _saveToPrefs();
}

Expand Down
15 changes: 15 additions & 0 deletions lib/features/settings/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:lucide_icons/lucide_icons.dart';
import 'package:mostro_mobile/core/app_theme.dart';
import 'package:mostro_mobile/features/relays/widgets/relay_selector.dart';
import 'package:mostro_mobile/features/settings/settings_provider.dart';
import 'package:mostro_mobile/features/settings/settings.dart';
import 'package:mostro_mobile/shared/widgets/currency_selection_dialog.dart';
import 'package:mostro_mobile/shared/providers/exchange_service_provider.dart';
import 'package:mostro_mobile/shared/widgets/language_selector.dart';
Expand Down Expand Up @@ -40,6 +41,15 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {

@override
Widget build(BuildContext context) {
// Listen to settings changes and update controllers
ref.listen<Settings>(settingsProvider, (previous, next) {
if (previous?.defaultLightningAddress != next.defaultLightningAddress) {
final newText = next.defaultLightningAddress ?? '';
if (_lightningAddressController.text != newText) {
_lightningAddressController.text = newText;
}
}
});

return Scaffold(
appBar: AppBar(
Expand Down Expand Up @@ -300,6 +310,11 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
onChanged: (value) {
final cleanValue = value.trim().isEmpty ? null : value.trim();
ref.read(settingsProvider.notifier).updateDefaultLightningAddress(cleanValue);

// Force sync immediately for empty values
if (cleanValue == null) {
_lightningAddressController.text = '';
}
},
decoration: InputDecoration(
border: InputBorder.none,
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
"defaultFiatCurrency": "Default Fiat Currency",
"setDefaultLightningAddress": "Set your default lightning address",
"defaultLightningAddress": "Default Lightning Address",
"lightningAddressUsed": "Using your configured Lightning address",
"relays": "Relays",
"selectNostrRelays": "Select the Nostr relays you connect to",
"addRelay": "Add Relay",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/intl_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@
"defaultFiatCurrency": "Moneda Fiat Predeterminada",
"setDefaultLightningAddress": "Establece tu dirección Lightning predeterminada",
"defaultLightningAddress": "Dirección Lightning",
"lightningAddressUsed": "Usando tu dirección Lightning configurada",
"relays": "Relays",
"selectNostrRelays": "Selecciona los relays Nostr a los que te conectas",
"addRelay": "Agregar Relay",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/intl_it.arb
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
"defaultFiatCurrency": "Valuta Fiat Predefinita",
"setDefaultLightningAddress": "Imposta il tuo indirizzo Lightning predefinito",
"defaultLightningAddress": "Indirizzo Lightning Predefinito",
"lightningAddressUsed": "Utilizzando il tuo indirizzo Lightning configurato",
"relays": "Relay",
"selectNostrRelays": "Seleziona i relay Nostr a cui connetterti",
"addRelay": "Aggiungi Relay",
Expand Down
3 changes: 3 additions & 0 deletions lib/shared/widgets/notification_listener_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class NotificationListenerWidget extends ConsumerWidget {
case 'sessionTimeoutMessage':
message = S.of(context)!.sessionTimeoutMessage;
break;
case 'lightningAddressUsed':
message = S.of(context)!.lightningAddressUsed;
break;
default:
message = next.customMessage!;
}
Expand Down
3 changes: 3 additions & 0 deletions test/mocks.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1944,6 +1944,7 @@ class MockSettings extends _i1.Mock implements _i2.Settings {
String? defaultFiatCode,
String? selectedLanguage,
String? defaultLightningAddress,
bool? clearDefaultLightningAddress = false,
List<String>? blacklistedRelays,
List<Map<String, dynamic>>? userRelays,
}) =>
Expand All @@ -1958,6 +1959,7 @@ class MockSettings extends _i1.Mock implements _i2.Settings {
#defaultFiatCode: defaultFiatCode,
#selectedLanguage: selectedLanguage,
#defaultLightningAddress: defaultLightningAddress,
#clearDefaultLightningAddress: clearDefaultLightningAddress,
#blacklistedRelays: blacklistedRelays,
#userRelays: userRelays,
},
Expand All @@ -1974,6 +1976,7 @@ class MockSettings extends _i1.Mock implements _i2.Settings {
#defaultFiatCode: defaultFiatCode,
#selectedLanguage: selectedLanguage,
#defaultLightningAddress: defaultLightningAddress,
#clearDefaultLightningAddress: clearDefaultLightningAddress,
#blacklistedRelays: blacklistedRelays,
#userRelays: userRelays,
},
Expand Down