diff --git a/lib/features/key_manager/key_manager.dart b/lib/features/key_manager/key_manager.dart index dd53161f..cce2f340 100644 --- a/lib/features/key_manager/key_manager.dart +++ b/lib/features/key_manager/key_manager.dart @@ -23,9 +23,6 @@ class KeyManager { } Future hasMasterKey() async { - if (masterKeyPair != null) { - return true; - } _masterKeyHex = await _storage.readMasterKey(); return _masterKeyHex != null; } diff --git a/lib/features/order/screens/take_order_screen.dart b/lib/features/order/screens/take_order_screen.dart index 4dc53655..2f0aae7c 100644 --- a/lib/features/order/screens/take_order_screen.dart +++ b/lib/features/order/screens/take_order_screen.dart @@ -1,16 +1,17 @@ import 'package:circular_countdown/circular_countdown.dart'; import 'package:dart_nostr/nostr/model/event/event.dart'; import 'package:flutter/material.dart'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:mostro_mobile/core/app_theme.dart'; +import 'package:mostro_mobile/data/models/enums/action.dart' as actions; import 'package:mostro_mobile/data/models/enums/order_type.dart'; import 'package:mostro_mobile/data/models/nostr_event.dart'; import 'package:mostro_mobile/features/order/providers/order_notifier_provider.dart'; import 'package:mostro_mobile/features/order/widgets/order_app_bar.dart'; +import 'package:mostro_mobile/shared/providers.dart'; import 'package:mostro_mobile/shared/widgets/order_cards.dart'; -import 'package:mostro_mobile/shared/providers/order_repository_provider.dart'; import 'package:mostro_mobile/shared/providers/exchange_service_provider.dart'; import 'package:mostro_mobile/shared/utils/currency_utils.dart'; import 'package:mostro_mobile/shared/widgets/custom_card.dart'; @@ -18,7 +19,7 @@ import 'package:mostro_mobile/features/mostro/mostro_instance.dart'; import 'package:mostro_mobile/shared/providers/time_provider.dart'; import 'package:mostro_mobile/generated/l10n.dart'; -class TakeOrderScreen extends ConsumerWidget { +class TakeOrderScreen extends ConsumerStatefulWidget { final String orderId; final OrderType orderType; final TextEditingController _fiatAmountController = TextEditingController(); @@ -28,13 +29,39 @@ class TakeOrderScreen extends ConsumerWidget { TakeOrderScreen({super.key, required this.orderId, required this.orderType}); @override - Widget build(BuildContext context, WidgetRef ref) { - final order = ref.watch(eventProvider(orderId)); + ConsumerState createState() => _TakeOrderScreenState(); +} + +class _TakeOrderScreenState extends ConsumerState { + bool _isSubmitting = false; + dynamic _lastSeenAction; + + @override + Widget build(BuildContext context) { + final order = ref.watch(eventProvider(widget.orderId)); + + // Listen for messages to reset loading state on CantDo + ref.listen( + mostroMessageStreamProvider(widget.orderId), + (_, next) { + next.whenData((msg) { + if (msg == null || msg.action == _lastSeenAction) return; + _lastSeenAction = msg.action; + + // Reset loading state only on CantDo message + if (msg.action == actions.Action.cantDo && _isSubmitting) { + setState(() { + _isSubmitting = false; + }); + } + }); + }, + ); return Scaffold( backgroundColor: AppTheme.backgroundDark, appBar: OrderAppBar( - title: orderType == OrderType.buy + title: widget.orderType == OrderType.buy ? S.of(context)!.buyOrderDetailsTitle : S.of(context)!.sellOrderDetailsTitle), body: SingleChildScrollView( @@ -94,7 +121,7 @@ class TakeOrderScreen extends ConsumerWidget { } } - final hasFixedSatsAmount = order.amount != null && order.amount != '0'; + final hasFixedSatsAmount = order.amount != '0'; return CustomCard( padding: const EdgeInsets.all(16), @@ -103,10 +130,10 @@ class TakeOrderScreen extends ConsumerWidget { children: [ Text( hasFixedSatsAmount - ? (orderType == OrderType.sell + ? (widget.orderType == OrderType.sell ? "${S.of(context)!.someoneIsSellingTitle.replaceAll(' Sats', '')} ${order.amount} Sats" : "${S.of(context)!.someoneIsBuyingTitle.replaceAll(' Sats', '')} ${order.amount} Sats") - : (orderType == OrderType.sell + : (widget.orderType == OrderType.sell ? S.of(context)!.someoneIsSellingTitle : S.of(context)!.someoneIsBuyingTitle), style: const TextStyle( @@ -153,7 +180,7 @@ class TakeOrderScreen extends ConsumerWidget { Widget _buildOrderId(BuildContext context) { return OrderIdCard( - orderId: orderId, + orderId: widget.orderId, ); } @@ -194,17 +221,17 @@ class TakeOrderScreen extends ConsumerWidget { Widget _buildActionButtons( BuildContext context, WidgetRef ref, NostrEvent order) { final orderDetailsNotifier = - ref.read(orderNotifierProvider(orderId).notifier); + ref.read(orderNotifierProvider(widget.orderId).notifier); final buttonText = - orderType == OrderType.buy ? S.of(context)!.sell : S.of(context)!.buy; + widget.orderType == OrderType.buy ? S.of(context)!.sell : S.of(context)!.buy; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: OutlinedButton( - onPressed: () => Navigator.of(context).pop(), + onPressed: () => context.pop(), style: AppTheme.theme.outlinedButtonTheme.style, child: Text(S.of(context)!.close), ), @@ -212,7 +239,10 @@ class TakeOrderScreen extends ConsumerWidget { const SizedBox(width: 16), Expanded( child: ElevatedButton( - onPressed: () async { + onPressed: _isSubmitting ? null : () async { + setState(() { + _isSubmitting = true; + }); // Check if this is a range order if (order.fiatAmount.maximum != null && order.fiatAmount.minimum != order.fiatAmount.maximum) { @@ -226,7 +256,7 @@ class TakeOrderScreen extends ConsumerWidget { return AlertDialog( title: Text(S.of(context)!.enterAmount), content: TextField( - controller: _fiatAmountController, + controller: widget._fiatAmountController, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: S.of(context)!.enterAmountBetween( @@ -237,14 +267,14 @@ class TakeOrderScreen extends ConsumerWidget { ), actions: [ TextButton( - onPressed: () => Navigator.of(context).pop(null), + onPressed: () => context.pop(), child: Text(S.of(context)!.cancel), ), ElevatedButton( key: const Key('submitAmountButton'), onPressed: () { final inputAmount = int.tryParse( - _fiatAmountController.text.trim()); + widget._fiatAmountController.text.trim()); if (inputAmount == null) { setState(() { errorText = @@ -264,7 +294,7 @@ class TakeOrderScreen extends ConsumerWidget { .toString()); }); } else { - Navigator.of(context).pop(inputAmount); + context.pop(inputAmount); } }, child: Text(S.of(context)!.submit), @@ -277,27 +307,32 @@ class TakeOrderScreen extends ConsumerWidget { ); if (enteredAmount != null) { - if (orderType == OrderType.buy) { + if (widget.orderType == OrderType.buy) { await orderDetailsNotifier.takeBuyOrder( order.orderId!, enteredAmount); } else { - final lndAddress = _lndAddressController.text.trim(); + final lndAddress = widget._lndAddressController.text.trim(); await orderDetailsNotifier.takeSellOrder( order.orderId!, enteredAmount, lndAddress.isEmpty ? null : lndAddress, ); } + } else { + // Dialog was dismissed without entering amount, reset loading state + setState(() { + _isSubmitting = false; + }); } } else { // Not a range order – use the existing logic. final fiatAmount = - int.tryParse(_fiatAmountController.text.trim()); - if (orderType == OrderType.buy) { + int.tryParse(widget._fiatAmountController.text.trim()); + if (widget.orderType == OrderType.buy) { await orderDetailsNotifier.takeBuyOrder( order.orderId!, fiatAmount); } else { - final lndAddress = _lndAddressController.text.trim(); + final lndAddress = widget._lndAddressController.text.trim(); await orderDetailsNotifier.takeSellOrder( order.orderId!, fiatAmount, @@ -309,7 +344,16 @@ class TakeOrderScreen extends ConsumerWidget { style: ElevatedButton.styleFrom( backgroundColor: AppTheme.mostroGreen, ), - child: Text(buttonText), + child: _isSubmitting + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Text(buttonText), ), ), ], diff --git a/lib/shared/notifiers/session_notifier.dart b/lib/shared/notifiers/session_notifier.dart index 2197a74d..c84d089c 100644 --- a/lib/shared/notifiers/session_notifier.dart +++ b/lib/shared/notifiers/session_notifier.dart @@ -67,6 +67,9 @@ class SessionNotifier extends StateNotifier> { Future newSession( {String? orderId, int? requestId, Role? role}) async { + if (state.any((s) => s.orderId == orderId)) { + return state.firstWhere((s) => s.orderId == orderId); + } final masterKey = ref.read(keyManagerProvider).masterKeyPair!; final keyIndex = await ref.read(keyManagerProvider).getCurrentKeyIndex(); final tradeKey = await ref.read(keyManagerProvider).deriveTradeKey(); diff --git a/pubspec.lock b/pubspec.lock index 0eb036f5..7b9f004e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "82.0.0" + version: "85.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c url: "https://pub.dev" source: hosted - version: "7.4.5" + version: "7.6.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: ee188b6df6c85f1441497c7171c84f1392affadc0384f71089cb10a3bc508cef + sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce url: "https://pub.dev" source: hosted - version: "0.13.1" + version: "0.13.4" app_links: dependency: "direct main" description: @@ -213,10 +213,10 @@ packages: dependency: transitive description: name: built_value - sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" + sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62" url: "https://pub.dev" source: hosted - version: "8.10.1" + version: "8.11.0" characters: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: coverage - sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080 + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.14.1" + version: "1.15.0" cross_file: dependency: transitive description: @@ -341,10 +341,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: cba5b6d7a6217312472bf4468cdf68c949488aed7ffb0eab792cd0b6c435054d + sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" url: "https://pub.dev" source: hosted - version: "1.0.0+7.4.5" + version: "1.0.0+7.7.0" dart_nostr: dependency: "direct main" description: @@ -357,10 +357,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" dbus: dependency: transitive description: @@ -442,10 +442,10 @@ packages: dependency: transitive description: name: flutter_background_service_android - sha256: b73d903056240e23a5c56d9e52d3a5d02ae41cb18b2988a97304ae37b2bae4bf + sha256: ca0793d4cd19f1e194a130918401a3d0b1076c81236f7273458ae96987944a87 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" flutter_background_service_ios: dependency: transitive description: @@ -551,10 +551,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: edae0c34573233ab03f5ba1f07465e55c384743893042cb19e010b4ee8541c12 + sha256: "20ca0a9c82ce0c855ac62a2e580ab867f3fbea82680a90647f7953832d0850ae" url: "https://pub.dev" source: hosted - version: "19.3.0" + version: "19.4.0" flutter_local_notifications_linux: dependency: transitive description: @@ -575,10 +575,10 @@ packages: dependency: transitive description: name: flutter_local_notifications_windows - sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e + sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" flutter_localizations: dependency: "direct main" description: flutter @@ -880,10 +880,10 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "63ad7ca6396290626dc0cb34725a939e4cfe965d80d36112f08d49cf13a8136e" + sha256: "82b2bdeee2199a510d3b7716121e96a6609da86693bb0863edd8566355406b79" url: "https://pub.dev" source: hosted - version: "1.0.49" + version: "1.0.50" local_auth_darwin: dependency: transitive description: @@ -912,10 +912,10 @@ packages: dependency: "direct main" description: name: logger - sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999" + sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.6.1" logging: dependency: transitive description: