Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
45 changes: 44 additions & 1 deletion lib/features/order/screens/add_lightning_invoice_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,49 @@ class _AddLightningInvoiceScreenState
return true;
}

/// Cancel button = cancel the trade itself (confirmed via dialog), not
/// just leave the screen — going back is what lands on trade detail (#268).
Future<void> _cancelOrder() async {
final l10n = AppLocalizations.of(context);
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(l10n.cancelTradeDialogTitle),
content: Text(l10n.cancelTradeDialogContent),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text(l10n.noButtonLabel),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: Text(l10n.yesCancelButtonLabel),
),
],
),
);
if (!mounted || confirmed != true) return;
try {
await orders_api.cancelOrder(orderId: widget.orderId);
if (!mounted) return;
_navigated = true;
refreshTrades(ref);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.cancelRequestSent)),
);
context.go(AppRoute.home);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
localizedDaemonError(l10n, e, fallback: l10n.cancelRequestFailed),
),
),
);
}
Comment thread
Catrya marked this conversation as resolved.
}

Future<void> _submit(WidgetRef ref) async {
if (_submitting) return;
final input = _invoiceController.text.trim();
Expand Down Expand Up @@ -271,7 +314,7 @@ class _AddLightningInvoiceScreenState
children: [
Expanded(
child: TextButton(
onPressed: () => context.pop(),
onPressed: _cancelOrder,
child: Text(
l10n.cancel,
style: TextStyle(color: colors?.textSecondary),
Expand Down
47 changes: 46 additions & 1 deletion lib/features/order/screens/pay_lightning_invoice_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import 'package:url_launcher/url_launcher.dart';

import 'package:mostro/core/app_routes.dart';
import 'package:mostro/core/app_theme.dart';
import 'package:mostro/core/daemon_errors.dart';
import 'package:mostro/features/order/providers/trade_state_provider.dart';
import 'package:mostro/features/settings/providers/nwc_provider.dart';
import 'package:mostro/features/trades/providers/trades_providers.dart'
show refreshTrades;
import 'package:mostro/l10n/app_localizations.dart';
import 'package:mostro/src/rust/api/orders.dart' as orders_api;
import 'package:mostro/src/rust/api/types.dart' show OrderStatus, TradeUpdate;
import 'package:mostro/shared/widgets/nwc_payment_widget.dart';

Expand Down Expand Up @@ -46,6 +48,49 @@ class _PayLightningInvoiceScreenState
setState(() => _waiting = true);
}

/// Cancel button = cancel the trade itself (confirmed via dialog), not
/// just leave the screen — going back is what lands on trade detail (#268).
Future<void> _cancelOrder() async {
final l10n = AppLocalizations.of(context);
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(l10n.cancelTradeDialogTitle),
content: Text(l10n.cancelTradeDialogContent),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text(l10n.noButtonLabel),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: Text(l10n.yesCancelButtonLabel),
),
],
),
);
if (!mounted || confirmed != true) return;
try {
await orders_api.cancelOrder(orderId: widget.orderId);
if (!mounted) return;
_navigated = true;
refreshTrades(ref);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.cancelRequestSent)),
);
context.go(AppRoute.home);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
localizedDaemonError(l10n, e, fallback: l10n.cancelRequestFailed),
),
),
);
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Expand Down Expand Up @@ -364,7 +409,7 @@ class _PayLightningInvoiceScreenState
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => context.pop(),
onPressed: _cancelOrder,
style: OutlinedButton.styleFrom(
foregroundColor:
colors?.destructiveRed ?? const Color(0xFFD84D4D),
Expand Down
16 changes: 16 additions & 0 deletions lib/features/order/screens/take_order_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class _TakeOrderScreenState extends ConsumerState<TakeOrderScreen> {
@override
void initState() {
super.initState();
// Defense in depth (#268): if the user already participates in this
// order (deep link, stale book entry, back navigation), Take Order
// must not offer to take it again — land on the trade instead.
_redirectIfParticipant();
// Try immediately in case the provider already has data.
_tryStartCountdown();
// If the provider is still loading, listen for the first value.
Expand All @@ -58,6 +62,12 @@ class _TakeOrderScreenState extends ConsumerState<TakeOrderScreen> {
});
}

Future<void> _redirectIfParticipant() async {
final role = await orders_api.getTradeRole(orderId: widget.orderId);
if (!mounted || role == null) return;
context.go(AppRoute.tradeDetailPath(widget.orderId));
}

void _tryStartCountdown() {
if (_countdownTimer != null) return; // already running
_startCountdown();
Expand Down Expand Up @@ -142,9 +152,15 @@ class _TakeOrderScreenState extends ConsumerState<TakeOrderScreen> {
// LN address was included in take-sell payload — go straight to trade.
context.go(AppRoute.tradeDetailPath(widget.orderId));
} else {
// Rebuild the stack with trade detail as the base so back/close
// from add-invoice lands on the trade, never back on Take Order
// offering to take an already-taken order (#268).
context.go(AppRoute.tradeDetailPath(widget.orderId));
context.push(AppRoute.addInvoicePath(widget.orderId));
}
} else {
// Same stack shape for the seller's pay-invoice screen (#268).
context.go(AppRoute.tradeDetailPath(widget.orderId));
context.push(AppRoute.payInvoicePath(widget.orderId));
}
} catch (e) {
Expand Down
Loading