Skip to content
Closed
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
4 changes: 3 additions & 1 deletion lib/background/background.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ Future<void> serviceMain(ServiceInstance service) async {

subscription.listen((event) async {
if (await eventStore.hasItem(event.id!)) return;
await retryNotification(event);
if (!isAppForeground) {
await showSimpleNotification(event);
}
});
});

Expand Down
3 changes: 2 additions & 1 deletion lib/background/desktop_background_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:logger/logger.dart';
import 'package:mostro_mobile/data/models/nostr_filter.dart';
import 'package:mostro_mobile/data/repositories.dart';
import 'package:mostro_mobile/features/settings/settings.dart';
import 'package:mostro_mobile/notifications/notification_service.dart';
import 'package:mostro_mobile/services/nostr_service.dart';
import 'package:mostro_mobile/shared/providers/mostro_database_provider.dart';
import 'abstract_background_service.dart';
Expand Down Expand Up @@ -75,7 +76,7 @@ class DesktopBackgroundService implements BackgroundService {
'event': event.toMap(),
});
if (!isAppForeground) {
//await showLocalNotification(event);
await showSimpleNotification(event);
}
});
break;
Expand Down
23 changes: 19 additions & 4 deletions lib/features/chat/screens/chat_room_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,26 @@ class ChatRoomScreen extends ConsumerStatefulWidget {
class _MessagesDetailScreenState extends ConsumerState<ChatRoomScreen> {
final TextEditingController _textController = TextEditingController();

/// Safely get nickname from peer public key, returns empty string if invalid
String _getSafeNickname(WidgetRef ref, String? publicKey) {
if (publicKey == null || publicKey.isEmpty) {
return '';
}

// Additional validation for hex string format
final hexRegex = RegExp(r'^[0-9a-fA-F]+$');
if (!hexRegex.hasMatch(publicKey.trim())) {
return '';
}

return ref.read(nickNameProvider(publicKey));
}

@override
Widget build(BuildContext context) {
final chatDetailState = ref.watch(chatRoomsProvider(widget.orderId));
final session = ref.read(sessionProvider(widget.orderId));
final peer = session!.peer!.publicKey;
final peer = session?.peer?.publicKey ?? '';

return Scaffold(
backgroundColor: AppTheme.dark1,
Expand Down Expand Up @@ -64,7 +79,7 @@ class _MessagesDetailScreenState extends ConsumerState<ChatRoomScreen> {
children: [
const SizedBox(height: 12.0),
Text('Order: ${widget.orderId}'),
_buildMessageHeader(peer, session),
if (session != null) _buildMessageHeader(peer, session),
_buildBody(chatDetailState, peer),
_buildMessageInput(),
const SizedBox(height: 12.0),
Expand Down Expand Up @@ -150,8 +165,8 @@ class _MessagesDetailScreenState extends ConsumerState<ChatRoomScreen> {
}

Widget _buildMessageHeader(String peerPubkey, Session session) {
final handle = ref.read(nickNameProvider(peerPubkey));
final you = ref.read(nickNameProvider(session.tradeKey.public));
final handle = _getSafeNickname(ref, peerPubkey);
final you = _getSafeNickname(ref, session.tradeKey.public);
final sharedKey = session.sharedKey?.private;

return Container(
Expand Down
21 changes: 18 additions & 3 deletions lib/features/chat/screens/chat_rooms_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,26 @@ class ChatListItem extends ConsumerWidget {

const ChatListItem({super.key, required this.orderId});

/// Safely get nickname from peer public key, returns empty string if invalid
String _getSafeNickname(WidgetRef ref, String? publicKey) {
if (publicKey == null || publicKey.isEmpty) {
return '';
}

// Additional validation for hex string format
final hexRegex = RegExp(r'^[0-9a-fA-F]+$');
if (!hexRegex.hasMatch(publicKey.trim())) {
return '';
}

return ref.read(nickNameProvider(publicKey));
}

@override
Widget build(BuildContext context, WidgetRef ref) {
final session = ref.watch(sessionProvider(orderId));
final pubkey = session!.peer!.publicKey;
final handle = ref.read(nickNameProvider(pubkey));
final pubkey = session?.peer?.publicKey;
final handle = _getSafeNickname(ref, pubkey);
return GestureDetector(
onTap: () {
context.push('/chat_room/$orderId');
Expand All @@ -92,7 +107,7 @@ class ChatListItem extends ConsumerWidget {
padding: const EdgeInsets.all(16),
child: Row(
children: [
NymAvatar(pubkeyHex: pubkey),
NymAvatar(pubkeyHex: pubkey ?? ''),
const SizedBox(width: 16),
Expanded(
child: Column(
Expand Down
35 changes: 20 additions & 15 deletions lib/features/trades/widgets/mostro_message_detail_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ class MostroMessageDetail extends ConsumerWidget {
final String orderId;
const MostroMessageDetail({super.key, required this.orderId});

/// Safely get nickname from peer public key, returns empty string if invalid
String _getSafeNickname(WidgetRef ref, String? publicKey) {
if (publicKey == null || publicKey.isEmpty) {
return '';
}

// Additional validation for hex string format
final hexRegex = RegExp(r'^[0-9a-fA-F]+$');
if (!hexRegex.hasMatch(publicKey.trim())) {
return '';
}

return ref.watch(nickNameProvider(publicKey));
}

@override
Widget build(BuildContext context, WidgetRef ref) {
final orderState = ref.watch(orderNotifierProvider(orderId));
Expand Down Expand Up @@ -112,9 +127,7 @@ class MostroMessageDetail extends ConsumerWidget {
return S.of(context)!.buyerInvoiceAccepted;
case actions.Action.holdInvoicePaymentAccepted:
final session = ref.watch(sessionProvider(orderPayload?.id ?? ''));
final sellerName = session?.peer?.publicKey != null
? ref.watch(nickNameProvider(session!.peer!.publicKey))
: '';
final sellerName = _getSafeNickname(ref, session?.peer?.publicKey);
return S.of(context)!.holdInvoicePaymentAccepted(
orderPayload?.fiatAmount.toString() ?? '',
orderPayload?.fiatCode ?? '',
Expand All @@ -124,9 +137,7 @@ class MostroMessageDetail extends ConsumerWidget {
sellerName,
);
case actions.Action.buyerTookOrder:
final buyerName = tradeState.peer?.publicKey != null
? ref.watch(nickNameProvider(tradeState.peer!.publicKey))
: '';
final buyerName = _getSafeNickname(ref, tradeState.peer?.publicKey);
return S.of(context)!.buyerTookOrder(
buyerName,
orderPayload!.fiatAmount.toString(),
Expand All @@ -135,23 +146,17 @@ class MostroMessageDetail extends ConsumerWidget {
);
case actions.Action.fiatSentOk:
final session = ref.watch(sessionProvider(orderPayload!.id ?? ''));
final peerName = tradeState.peer?.publicKey != null
? ref.watch(nickNameProvider(tradeState.peer!.publicKey))
: '';
final peerName = _getSafeNickname(ref, tradeState.peer?.publicKey);
return session!.role == Role.buyer
? S.of(context)!.fiatSentOkBuyer(peerName)
: S.of(context)!.fiatSentOkSeller(peerName);
case actions.Action.released:
final sellerName = tradeState.peer?.publicKey != null
? ref.watch(nickNameProvider(tradeState.peer!.publicKey))
: '';
final sellerName = _getSafeNickname(ref, tradeState.peer?.publicKey);
return S.of(context)!.released(sellerName);
case actions.Action.purchaseCompleted:
return S.of(context)!.purchaseCompleted;
case actions.Action.holdInvoicePaymentSettled:
final buyerName = tradeState.peer?.publicKey != null
? ref.watch(nickNameProvider(tradeState.peer!.publicKey))
: '';
final buyerName = _getSafeNickname(ref, tradeState.peer?.publicKey);
return S.of(context)!.holdInvoicePaymentSettled(buyerName);
case actions.Action.rate:
return S.of(context)!.rate;
Expand Down
49 changes: 46 additions & 3 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
}
}
},
"waitingSellerToPay": "Please wait. I’ve sent a payment request to the seller to send the Sats for the order ID {id}. If the seller doesn’t complete the payment within {expiration_seconds} minutes, the trade will be canceled.",
"payInvoiceGeneric": "Please pay the hold invoice to continue the trade.",
"addInvoiceGeneric": "Please send an invoice to continue the trade.",
"waitingSellerToPay": "Please wait. I've sent a payment request to the seller to send the Sats for the order ID {id}. If the seller doesn't complete the payment within {expiration_seconds} minutes, the trade will be canceled.",
"@waitingSellerToPay": {
"placeholders": {
"expiration_seconds": {
Expand Down Expand Up @@ -77,7 +79,7 @@
"rate": "Please rate your counterparty",
"rateReceived": "Rating successfully saved!",
"cooperativeCancelInitiatedByYou": "You have initiated the cancellation of the order ID: {id}. Your counterparty must agree. If they do not respond, you can open a dispute. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.",
"cooperativeCancelInitiatedByPeer": "Your counterparty wants to cancel order ID: {id}. If you agree, please send me cancel-order-message. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.",
"cooperativeCancelInitiatedByPeer": "Your counterparty wants to cancel order ID: {id}. If you agree, please cancel this order as well by pressing the cancel button. Please note that no administrator will contact you regarding this cancellation unless you open a dispute first.",
"cooperativeCancelAccepted": "Order {id} has been successfully canceled!",
"disputeInitiatedByYou": "You have initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute token is: {user_token}.",
"disputeInitiatedByPeer": "Your counterparty has initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute token is: {user_token}.",
Expand Down Expand Up @@ -654,5 +656,46 @@
"editRelay": "Edit Relay",
"relayUrl": "Relay URL",
"add": "Add",
"save": "Save"
"save": "Save",

"@_comment_notifications": "Notification System",
"notificationChannelName": "Mostro Notifications",
"notificationChannelDescription": "Notifications for Mostro trades and messages",
"notificationNewMostroMessage": "New Mostro Message",
"notificationNewEncryptedMessage": "You have received a new encrypted message",

"@_comment_notification_titles": "Notification Titles",
"notificationOrderPublished": "Order Published",
"notificationOrderPublishedBody": "Please wait for someone to take your order. You can cancel it at any time.",
"notificationOrderTaken": "Order Taken",
"notificationPaymentRequired": "Payment Required",
"notificationInvoiceRequired": "Invoice Required",
"notificationPaymentSent": "Payment Sent",
"notificationBitcoinReleased": "Bitcoin has been released! Your invoice should be paid soon.",
"notificationOrderCanceled": "Order Canceled",
"notificationDisputeStarted": "Dispute Started",
"notificationTradeComplete": "Trade Complete",
"notificationPaymentFailed": "Payment Failed",
"notificationRequestFailed": "Request Failed",
"notificationMostroUpdate": "Mostro Update",
"notificationCooperativeCancelInitiatedByPeer": "Cooperative Cancel Initiated by Peer",
"notificationCooperativeCancelInitiatedByYou": "Cooperative Cancel Initiated by You",
"notificationCooperativeCancelAccepted": "Cooperative Cancel Accepted",
"notificationTradeCompleted": "Trade completed successfully!",

"@_comment_notification_messages": "Notification Messages",
"notificationOrderTakenMessage": "Someone has taken your order. Check the trade details in My Trades.",
"notificationPaymentSentMessage": "Fiat payment has been sent. Waiting for confirmation.",
"notificationOrderCanceledMessage": "Order has been canceled.",
"notificationUnableToProcessMessage": "Unable to process the request.",
"notificationUnableToProcessReasonMessage": "Unable to process: {reason}",
"@notificationUnableToProcessReasonMessage": {
"placeholders": {
"reason": {
"type": "String",
"description": "The specific reason why the request failed"
}
}
},
"notificationNewMessageReceived": "New message received"
}
68 changes: 66 additions & 2 deletions lib/l10n/intl_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
}
}
},
"payInvoiceGeneric": "Por favor paga la factura retenida para continuar el intercambio.",
"addInvoiceGeneric": "Por favor envía una factura para continuar el intercambio.",
"waitingSellerToPay": "Por favor espera. He enviado una solicitud de pago al vendedor para enviar los Sats para la orden con ID {id}. Si el vendedor no completa el pago dentro de {expiration_seconds} minutos, el intercambio será cancelado.",
"@waitingSellerToPay": {
"placeholders": {
Expand Down Expand Up @@ -77,7 +79,7 @@
"rate": "Por favor califica a tu contraparte",
"rateReceived": "¡Calificación guardada exitosamente!",
"cooperativeCancelInitiatedByYou": "Has iniciado la cancelación de la orden ID: {id}. Tu contraparte debe estar de acuerdo. Si no responden, puedes abrir una disputa. Ten en cuenta que ningún administrador te contactará sobre esta cancelación a menos que abras una disputa primero.",
"cooperativeCancelInitiatedByPeer": "Tu contraparte quiere cancelar la orden ID: {id}. Si estás de acuerdo, por favor envíame cancel-order-message. Ten en cuenta que ningún administrador te contactará sobre esta cancelación a menos que abras una disputa primero.",
"cooperativeCancelInitiatedByPeer": "Tu contraparte quiere cancelar la orden ID: {id}. Si estás de acuerdo, por favor cancela tu también esta orden presionando el boton de cancelar. Ten en cuenta que ningún administrador te contactará sobre esta cancelación a menos que abras una disputa primero.",
"cooperativeCancelAccepted": "¡La orden {id} ha sido cancelada exitosamente!",
"disputeInitiatedByYou": "Has iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu token de disputa es: {user_token}.",
"disputeInitiatedByPeer": "Tu contraparte ha iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu token de disputa es: {user_token}.",
Expand Down Expand Up @@ -686,5 +688,67 @@
"editRelay": "Editar Relay",
"relayUrl": "URL del Relay",
"add": "Agregar",
"save": "Guardar"
"save": "Guardar",

"@_comment_notification_strings": "Cadenas de Notificación",
"notificationOrderPublished": "Orden Publicada",
"notificationOrderPublishedBody": "Espera que alguien tome tu orden. Puedes cancelarla en cualquier momento.",
"notificationOrderTaken": "Orden Tomada",
"notificationPaymentRequired": "Pago Requerido",
"notificationInvoiceRequired": "Factura Requerida",
"notificationPaymentSent": "Pago Enviado",
"notificationOrderCanceled": "Orden Cancelada",
"notificationDisputeStarted": "Disputa Iniciada",
"notificationTradeComplete": "Intercambio Completado",
"notificationPaymentFailed": "Pago Fallido",
"notificationRequestFailed": "Solicitud Fallida",
"notificationMostroUpdate": "Actualización de Mostro",
"notificationCooperativeCancelInitiatedByPeer": "Cancelación Cooperativa Iniciada por la contraparte",
"notificationCooperativeCancelInitiatedByYou": "Cancelación Cooperativa Iniciada por Ti",
"notificationCooperativeCancelAccepted": "Cancelación Cooperativa Aceptada",
"notificationChannelName": "Notificaciones de Mostro",
"notificationChannelDescription": "Notificaciones para intercambios y mensajes de Mostro",
"notificationNewMostroMessage": "Nuevo Mensaje de Mostro",
"notificationNewEncryptedMessage": "Has recibido un nuevo mensaje cifrado",
"notificationEncryptedMessageReceived": "Has recibido un nuevo mensaje encriptado",
"notificationOfferPublished": "¡Tu oferta ha sido publicada! Por favor espera hasta que otro usuario elija tu orden.",
"notificationSomeoneTookOrder": "Alguien ha tomado tu orden. Verifica los detalles del intercambio.",
"notificationPayHoldInvoice": "Por favor paga la factura retenida",
"notificationPayHoldInvoiceAmount": "Por favor paga la factura retenida de {amount} Sats",
"@notificationPayHoldInvoiceAmount": {
"placeholders": {
"amount": {
"type": "String"
}
}
},
"notificationSendInvoice": "Por favor envía una factura",
"notificationSendInvoiceAmount": "Por favor envía una factura por {amount} satoshis",
"@notificationSendInvoiceAmount": {
"placeholders": {
"amount": {
"type": "String"
}
}
},
"notificationFiatSent": "El pago fiat ha sido enviado. Esperando confirmación.",
"notificationBitcoinReleased": "¡Bitcoin ha sido liberado! Tu factura debería ser pagada pronto.",
"notificationOrderCanceled": "La orden ha sido cancelada.",
"notificationCooperativeCancelRequest": "Tu contraparte quiere cancelar la orden",
"notificationDisputeInitiatedByYou": "Has iniciado una disputa. Un resolutor será asignado pronto.",
"notificationDisputeInitiatedByPeer": "Tu contraparte ha iniciado una disputa.",
"notificationTradeCompleted": "¡Intercambio completado exitosamente!",
"notificationOrderTakenMessage": "Alguien ha tomado tu orden. Revisa los detalles my en Mis Intercambios.",
"notificationPaymentSentMessage": "Se ha enviado el pago en moneda fiduciaria. Esperando confirmación.",
"notificationOrderCanceledMessage": "La orden ha sido cancelada.",
"notificationUnableToProcess": "No se puede procesar la solicitud.",
"notificationUnableToProcessReason": "No se puede procesar: {reason}",
"@notificationUnableToProcessReason": {
"placeholders": {
"reason": {
"type": "String"
}
}
},
"notificationNewMessageReceived": "Nuevo mensaje recibido"
}
Loading