diff --git a/lib/features/order/screens/order_confirmation_screen.dart b/lib/features/order/screens/order_confirmation_screen.dart index 1d6d4ac4..c26540d5 100644 --- a/lib/features/order/screens/order_confirmation_screen.dart +++ b/lib/features/order/screens/order_confirmation_screen.dart @@ -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; @@ -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'), @@ -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, ), diff --git a/lib/features/trades/screens/trade_detail_screen.dart b/lib/features/trades/screens/trade_detail_screen.dart index 67394bd2..b8a9d810 100644 --- a/lib/features/trades/screens/trade_detail_screen.dart +++ b/lib/features/trades/screens/trade_detail_screen.dart @@ -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); @@ -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'; } } @@ -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), @@ -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'; } } @@ -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, @@ -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( @@ -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, ), ); @@ -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 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 @@ -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 @@ -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)))) { diff --git a/lib/features/trades/widgets/mostro_message_detail_widget.dart b/lib/features/trades/widgets/mostro_message_detail_widget.dart index b7ba3c2a..34f9f40d 100644 --- a/lib/features/trades/widgets/mostro_message_detail_widget.dart +++ b/lib/features/trades/widgets/mostro_message_detail_widget.dart @@ -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'; @@ -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: @@ -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 @@ -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); @@ -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, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index aa255c64..f890b5f8 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -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": { @@ -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}", @@ -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" + } \ No newline at end of file diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index f97ffa0b..8634ca29 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -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": { @@ -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}", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 5ca41f24..92cf29cc 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -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": { @@ -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": { diff --git a/pubspec.lock b/pubspec.lock index 31690a4d..031683bb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -445,10 +445,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" url: "https://pub.dev" source: hosted - version: "0.9.5" + version: "0.9.4+4" file_selector_platform_interface: dependency: transitive description: @@ -851,10 +851,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" url: "https://pub.dev" source: hosted - version: "0.8.13+10" + version: "0.8.13+1" image_picker_for_web: dependency: transitive description: @@ -867,10 +867,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "997d100ce1dda5b1ba4085194c5e36c9f8a1fb7987f6a36ab677a344cd2dc986" + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e url: "https://pub.dev" source: hosted - version: "0.8.13+2" + version: "0.8.13" image_picker_linux: dependency: transitive description: @@ -883,10 +883,10 @@ packages: dependency: transitive description: name: image_picker_macos - sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 url: "https://pub.dev" source: hosted - version: "0.2.2+1" + version: "0.2.2" image_picker_platform_interface: dependency: transitive description: @@ -952,26 +952,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -1638,26 +1638,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.8" timeago: dependency: "direct main" description: @@ -1790,10 +1790,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: @@ -1875,5 +1875,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.9.0 <=3.9.9" - flutter: ">=3.35.0" + dart: ">=3.8.0 <=3.9.9" + flutter: ">=3.32.0"