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
6 changes: 5 additions & 1 deletion lib/features/order/screens/order_confirmation_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:mostro_mobile/core/app_theme.dart';
import 'package:mostro_mobile/features/order/widgets/order_app_bar.dart';
import 'package:mostro_mobile/generated/l10n.dart';
import 'package:mostro_mobile/shared/widgets/custom_card.dart';
import 'package:mostro_mobile/features/mostro/mostro_instance.dart';
import 'package:mostro_mobile/shared/providers/order_repository_provider.dart';

class OrderConfirmationScreen extends ConsumerWidget {
final String orderId;
Expand All @@ -13,6 +15,8 @@ class OrderConfirmationScreen extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final mostroInstance = ref.watch(orderRepositoryProvider).mostroInstance;
final expirationHours = mostroInstance?.expirationHours ?? 24;
return Scaffold(
backgroundColor: AppTheme.dark1,
appBar: OrderAppBar(title: 'Order Confirmed'),
Expand All @@ -23,7 +27,7 @@ class OrderConfirmationScreen extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
S.of(context)!.newOrder('24'),
S.of(context)!.newOrder(expirationHours.toString()),
style: TextStyle(fontSize: 18, color: AppTheme.cream1),
textAlign: TextAlign.center,
),
Expand Down
61 changes: 36 additions & 25 deletions lib/features/trades/screens/trade_detail_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ class TradeDetailScreen extends ConsumerWidget {
BuildContext context, WidgetRef ref, OrderState tradeState) {
final session = ref.watch(sessionProvider(orderId));
final isPending = tradeState.status == Status.pending;
final mostroInstance = ref.watch(orderRepositoryProvider).mostroInstance;
final expirationHours = mostroInstance?.expirationHours ?? 24;

// Determine if the user is the creator of the order based on role and order type
final isCreator = _isUserCreator(session, tradeState);
Expand All @@ -119,20 +121,19 @@ class TradeDetailScreen extends ConsumerWidget {
// If `orderPayload.amount` is 0, the trade is "at market price"
final isZeroAmount = (tradeState.order!.amount == 0);
String priceText = '';

if (isZeroAmount) {
final premium = tradeState.order!.premium;
final premiumValue = premium.toDouble();

if (premiumValue == 0) {
// No premium - show only market price
priceText = S.of(context)!.atMarketPrice;
} else {
// Has premium/discount - show market price with percentage
final isPremiumPositive = premiumValue >= 0;
final premiumDisplay = isPremiumPositive
? '(+$premiumValue%)'
: '($premiumValue%)';
final premiumDisplay =
isPremiumPositive ? '(+$premiumValue%)' : '($premiumValue%)';
priceText = '${S.of(context)!.atMarketPrice} $premiumDisplay';
}
}
Expand All @@ -153,7 +154,9 @@ class TradeDetailScreen extends ConsumerWidget {
return Column(
children: [
NotificationMessageCard(
message: S.of(context)!.youCreatedOfferMessage,
message: S
.of(context)!
.youCreatedOfferMessage(expirationHours.toString()),
icon: Icons.info_outline,
),
const SizedBox(height: 16),
Expand Down Expand Up @@ -188,23 +191,22 @@ class TradeDetailScreen extends ConsumerWidget {

final hasFixedSatsAmount = tradeState.order!.amount != 0;
final satAmount = hasFixedSatsAmount ? ' ${tradeState.order!.amount}' : '';

// For market price orders, show premium in the same format as order book
String priceText = '';

if (!hasFixedSatsAmount) {
final premium = tradeState.order!.premium;
final premiumValue = premium.toDouble();

if (premiumValue == 0) {
// No premium - show only market price
priceText = S.of(context)!.atMarketPrice;
} else {
// Has premium/discount - show market price with percentage
final isPremiumPositive = premiumValue >= 0;
final premiumDisplay = isPremiumPositive
? '(+$premiumValue%)'
: '($premiumValue%)';
final premiumDisplay =
isPremiumPositive ? '(+$premiumValue%)' : '($premiumValue%)';
priceText = '${S.of(context)!.atMarketPrice} $premiumDisplay';
}
}
Expand Down Expand Up @@ -620,7 +622,8 @@ class TradeDetailScreen extends ConsumerWidget {
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)!.yes,
Expand Down Expand Up @@ -717,7 +720,7 @@ class TradeDetailScreen extends ConsumerWidget {
// Create dispute using the repository
final repository = ref.read(disputeRepositoryProvider);
final success = await repository.createDispute(orderId);

if (success && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
Expand All @@ -737,7 +740,9 @@ class TradeDetailScreen extends ConsumerWidget {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(S.of(context)!.disputeCreationErrorWithMessage(e.toString())),
content: Text(S
.of(context)!
.disputeCreationErrorWithMessage(e.toString())),
backgroundColor: AppTheme.red1,
),
);
Expand Down Expand Up @@ -781,17 +786,22 @@ class TradeDetailScreen extends ConsumerWidget {
Widget _buildButtonRow(
BuildContext context, WidgetRef ref, OrderState tradeState) {
final actionButtons = _buildActionButtons(context, ref, tradeState);

// Check for VIEW DISPUTE button - independent of action system
final List<Widget> extraButtons = [];
if ((tradeState.action == actions.Action.disputeInitiatedByYou ||
tradeState.action == actions.Action.disputeInitiatedByPeer ||
tradeState.action == actions.Action.adminTookDispute) &&
tradeState.dispute?.disputeId != null) {
extraButtons.add(_buildViewDisputeButton(context, tradeState.dispute!.disputeId));
tradeState.action == actions.Action.disputeInitiatedByPeer ||
tradeState.action == actions.Action.adminTookDispute) &&
tradeState.dispute?.disputeId != null) {
extraButtons
.add(_buildViewDisputeButton(context, tradeState.dispute!.disputeId));
}

final allButtons = [_buildCloseButton(context), ...actionButtons, ...extraButtons];

final allButtons = [
_buildCloseButton(context),
...actionButtons,
...extraButtons
];

if (allButtons.length == 1) {
// Single button - center it with natural oval shape
Expand Down Expand Up @@ -976,7 +986,6 @@ class _CountdownWidget extends ConsumerWidget {
final status = tradeState.status;
final now = DateTime.now();
final mostroInstance = ref.read(orderRepositoryProvider).mostroInstance;

// Show countdown ONLY for these 3 specific statuses
if (status == Status.pending) {
// Pending orders: use exact timestamps from expires_at
Expand All @@ -985,8 +994,10 @@ class _CountdownWidget extends ConsumerWidget {
return null;
}

final expiration = DateTime.fromMillisecondsSinceEpoch(expiresAtTimestamp);
final createdAt = DateTime.fromMillisecondsSinceEpoch((tradeState.order?.createdAt ?? 0) * 1000);
final expiration =
DateTime.fromMillisecondsSinceEpoch(expiresAtTimestamp);
final createdAt = DateTime.fromMillisecondsSinceEpoch(
(tradeState.order?.createdAt ?? 0) * 1000);

// Handle edge case: expiration in the past
if (expiration.isBefore(now.subtract(const Duration(hours: 1)))) {
Expand Down
28 changes: 17 additions & 11 deletions lib/features/trades/widgets/mostro_message_detail_widget.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mostro_mobile/data/enums.dart';
import 'package:mostro_mobile/data/models/nostr_event.dart';
import 'package:mostro_mobile/data/models/session.dart';
import 'package:mostro_mobile/features/order/models/order_state.dart';
import 'package:mostro_mobile/features/order/providers/order_notifier_provider.dart';
Expand Down Expand Up @@ -61,9 +60,9 @@ class MostroMessageDetail extends ConsumerWidget {
switch (action) {
case actions.Action.newOrder:
final expHrs =
ref.read(orderRepositoryProvider).mostroInstance?.expiration ??
'24';
return S.of(context)!.newOrder(int.tryParse(expHrs) ?? 24);
ref.read(orderRepositoryProvider).mostroInstance?.expirationHours ??
24;
return S.of(context)!.newOrder(expHrs.toString());
case actions.Action.canceled:
return S.of(context)!.canceled(orderPayload?.id ?? '');
case actions.Action.payInvoice:
Expand Down Expand Up @@ -107,14 +106,17 @@ class MostroMessageDetail extends ConsumerWidget {
?.expirationSeconds ??
900;
final expMinutes = (expSecs / 60).round();

// Check if user is the maker (creator) of the order
final session = ref.watch(sessionProvider(orderPayload?.id ?? ''));
final isUserCreator = _isUserCreator(session, tradeState);

if (isUserCreator) {
// Maker scenario: user created a buy order, show waiting for taker message
return S.of(context)!.orderTakenWaitingCounterpart(expMinutes).replaceAll('\\n', '\n');
return S
.of(context)!
.orderTakenWaitingCounterpart(expMinutes)
.replaceAll('\\n', '\n');
} else {
// Taker scenario: user took someone's order, show original message
return S
Expand All @@ -128,15 +130,18 @@ class MostroMessageDetail extends ConsumerWidget {
?.expirationSeconds ??
900;
final expMinutes = (expSecs / 60).round();

// Check if user is the maker (creator) of a sell order
final session = ref.watch(sessionProvider(orderPayload?.id ?? ''));
final isUserCreator = _isUserCreator(session, tradeState);
final isSellOrder = tradeState.order?.kind == OrderType.sell;

if (isUserCreator && isSellOrder) {
// Maker scenario: user created a sell order, show waiting for taker message
return S.of(context)!.orderTakenWaitingCounterpart(expMinutes).replaceAll('\\n', '\n');
return S
.of(context)!
.orderTakenWaitingCounterpart(expMinutes)
.replaceAll('\\n', '\n');
} else {
// Taker scenario: user took someone's order, show original message
return S.of(context)!.waitingBuyerInvoice(expMinutes);
Expand Down Expand Up @@ -218,7 +223,8 @@ class MostroMessageDetail extends ConsumerWidget {
return S.of(context)!.adminSettledUsers(orderPayload!.id ?? '');
case actions.Action.paymentFailed:
final payload = ref.read(orderNotifierProvider(orderId)).paymentFailed;
final intervalInMinutes = ((payload?.paymentRetriesInterval ?? 0) / 60).round();
final intervalInMinutes =
((payload?.paymentRetriesInterval ?? 0) / 60).round();
return S.of(context)!.paymentFailed(
payload?.paymentAttempts ?? 0,
intervalInMinutes,
Expand Down
21 changes: 19 additions & 2 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
"@@locale": "en",
"backToHome": "Back to Home",
"newOrder": "Your offer has been published! Please wait until another user picks your order. It will be available for {expiration_hours} hours. You can cancel this order before another user picks it up by executing: cancel.",
"@newOrder": {
"placeholders": {
"expiration_hours": {
"type": "String",
"description": "The expiration time in hours"
}
}
},
"canceled": "You have canceled the order ID: {id}.",
"payInvoice": "Please pay this hold invoice of {amount} Sats for {fiat_code} {fiat_amount} to start the operation. If you do not pay it within {expiration_seconds} minutes, the trade will be canceled.",
"@payInvoice": {
Expand Down Expand Up @@ -263,7 +271,15 @@
"someoneIsBuyingTitle": "Someone is Buying Sats",
"someoneIsSellingFixedTitle": "Someone is Selling {sats} Sats",
"someoneIsBuyingFixedTitle": "Someone is Buying {sats} Sats",
"youCreatedOfferMessage": "You created this offer. Below are the details of your offer. Wait for another user to take it. It will be published for 24 hours. You can cancel it at any time using the 'Cancel' button.",
"youCreatedOfferMessage": "You created this offer. Below are the details of your offer. Wait for another user to take it. It will be published for {expiration_hours} hours. You can cancel it at any time using the 'Cancel' button.",
"@youCreatedOfferMessage": {
"placeholders": {
"expiration_hours": {
"type": "String",
"description": "The expiration time in hours"
}
}
},
"youAreSellingTitle": "You are selling{sats} sats",
"youAreBuyingTitle": "You are buying{sats} sats",
"forAmount": "for {amount}",
Expand Down Expand Up @@ -1217,5 +1233,6 @@

"@_comment_backup_reminder": "Backup reminder notification",
"backupAccountReminder": "You must back up your account",
"backupAccountReminderMessage": "Back up your secret words to recover your account"
"backupAccountReminderMessage": "Save your secret words to recover your account"

}
16 changes: 15 additions & 1 deletion lib/l10n/intl_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
"@@locale": "es",
"backToHome": "Volver al inicio",
"newOrder": "¡Tu oferta ha sido publicada! Por favor espera hasta que otro usuario elija tu orden. Estará disponible durante {expiration_hours} horas. Puedes cancelar esta orden antes de que otro usuario la tome ejecutando: cancel.",
"@newOrder": {
"placeholders": {
"expiration_hours": {
"type": "String"
}
}
},
"canceled": "Has cancelado la orden ID: {id}.",
"payInvoice": "Por favor paga esta factura retenida de {amount} Sats por {fiat_amount} {fiat_code} para iniciar la operación. Si no la pagas antes de {expiration_seconds} minutos, el intercambio será cancelado.",
"@payInvoice": {
Expand Down Expand Up @@ -611,7 +618,14 @@
"someoneIsBuyingTitle": "Alguien está Comprando Sats",
"someoneIsSellingFixedTitle": "Alguien está Vendiendo {sats} Sats",
"someoneIsBuyingFixedTitle": "Alguien está Comprando {sats} Sats",
"youCreatedOfferMessage": "Creaste esta oferta. A continuación se muestran los detalles de tu oferta. Espera a que otro usuario la tome. Se publicará durante 24 horas. Puedes cancelarla en cualquier momento usando el botón 'Cancelar'.",
"youCreatedOfferMessage": "Creaste esta oferta. A continuación se muestran los detalles de tu oferta. Espera a que otro usuario la tome. Se publicará durante {expiration_hours} horas. Puedes cancelarla en cualquier momento usando el botón 'Cancelar'.",
"@youCreatedOfferMessage": {
"placeholders": {
"expiration_hours": {
"type": "String"
}
}
},
"youAreSellingTitle": "Estás vendiendo {sats} sats",
"youAreBuyingTitle": "Estás comprando {sats} sats",
"forAmount": "por {amount}",
Expand Down
16 changes: 15 additions & 1 deletion lib/l10n/intl_it.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
"@@locale": "it",
"backToHome": "Torna alla home",
"newOrder": "La tua offerta è stata pubblicata! Attendi fino a quando un altro utente accetta il tuo ordine. Sarà disponibile per {expiration_hours} ore. Puoi annullare questo ordine prima che un altro utente lo prenda premendo: Cancella.",
"@newOrder": {
"placeholders": {
"expiration_hours": {
"type": "String"
}
}
},
"canceled": "Hai annullato l'ordine ID: {id}!",
"payInvoice": "Paga questa Hodl Invoice di {amount} Sats per {fiat_amount} {fiat_code} per iniziare l'operazione. Se non la paghi entro {expiration_seconds} minuti lo scambio sarà annullato.",
"@payInvoice": {
Expand Down Expand Up @@ -654,7 +661,14 @@
}
}
},
"youCreatedOfferMessage": "Hai creato questa offerta. Di seguito sono riportati i dettagli della tua offerta. Aspetta che un altro utente la prenda. Sarà pubblicata per 24 ore. Puoi cancellarla in qualsiasi momento utilizzando il pulsante 'Annulla'.",
"youCreatedOfferMessage": "Hai creato questa offerta. Di seguito sono riportati i dettagli della tua offerta. Aspetta che un altro utente la prenda. Sarà pubblicata per {expiration_hours} ore. Puoi cancellarla in qualsiasi momento utilizzando il pulsante 'Annulla'.",
"@youCreatedOfferMessage": {
"placeholders": {
"expiration_hours": {
"type": "String"
}
}
},
"youAreSellingTitle": "Stai vendendo {sats} sats",
"@youAreSellingTitle": {
"placeholders": {
Expand Down
Loading