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
1 change: 0 additions & 1 deletion lib/features/home/providers/home_order_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ final orderBookProvider = StreamProvider.autoDispose<List<OrderItem>>((ref) asyn
while (true) {
final orders = await stream.next();
if (orders == null) break;
debugPrint('[orderBook] update: ${orders.length} orders');
yield orders.map(OrderItem.fromInfo).toList();
}
});
Expand Down
21 changes: 20 additions & 1 deletion lib/features/order/screens/add_order_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import 'package:mostro/features/order/widgets/currency_section.dart';
import 'package:mostro/features/settings/providers/settings_provider.dart';
import 'package:mostro/features/order/widgets/payment_method_section.dart';
import 'package:mostro/features/order/widgets/price_section.dart';
import 'package:mostro/core/services/identity_service.dart';
import 'package:mostro/features/trades/providers/trades_providers.dart'
show refreshTrades;
import 'package:mostro/src/rust/api/identity.dart' as identity_api;
import 'package:mostro/src/rust/api/orders.dart' as rust_orders;
import 'package:mostro/src/rust/api/types.dart';

Expand Down Expand Up @@ -125,14 +127,31 @@ class _AddOrderScreenState extends ConsumerState<AddOrderScreen> {
);

await rust_orders.createOrder(params: params);

// Persist the updated trade key index so it survives app restarts.
// Failures here are non-fatal — the order was already created.
try {
final identity = await identity_api.getIdentity();
if (identity != null) {
await IdentityService.saveTradeKeyIndex(identity.tradeKeyIndex);
}
} catch (e) {
debugPrint('[orders] save tradeKeyIndex failed: $e');
}

refreshTrades(ref);

if (!mounted) return;
context.go(AppRoute.orderBook);
} catch (e) {
if (!mounted) return;
// CantDo rejections from Mostro arrive as errors from createOrder.
// Strip the Rust error prefix for a cleaner message.
final raw = e.toString();
final anyhowMatch = RegExp(r'^.*?AnyhowException\((.+)\)$').firstMatch(raw);
final msg = anyhowMatch != null ? anyhowMatch.group(1)! : raw;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to create order: $e')),
SnackBar(content: Text(msg)),
);
} finally {
if (mounted) setState(() => _submitting = false);
Expand Down
6 changes: 4 additions & 2 deletions lib/features/trades/providers/trades_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import 'package:mostro/src/rust/api/types.dart' as rust_types;
enum TradeStatusFilter {
all('All'),
pending('Pending'),
waitingInvoice('Waiting Invoice'),
waitingPayment('Waiting Payment'),
active('Active'),
fiatSent('Fiat Sent'),
success('Success'),
Expand Down Expand Up @@ -75,8 +77,8 @@ class TradeListItem {
TradeStatusFilter orderStatusToFilter(rust_types.OrderStatus status) {
return switch (status) {
rust_types.OrderStatus.pending => TradeStatusFilter.pending,
rust_types.OrderStatus.waitingBuyerInvoice => TradeStatusFilter.pending,
rust_types.OrderStatus.waitingPayment => TradeStatusFilter.pending,
rust_types.OrderStatus.waitingBuyerInvoice => TradeStatusFilter.waitingInvoice,
rust_types.OrderStatus.waitingPayment => TradeStatusFilter.waitingPayment,
rust_types.OrderStatus.active => TradeStatusFilter.active,
rust_types.OrderStatus.inProgress => TradeStatusFilter.active,
rust_types.OrderStatus.fiatSent => TradeStatusFilter.fiatSent,
Expand Down
2 changes: 2 additions & 0 deletions lib/features/trades/widgets/trades_list_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ class TradesListItem extends ConsumerWidget {
static (Color, Color) _statusColors(TradeStatusFilter status) {
return switch (status) {
TradeStatusFilter.pending => AppColors.statusPending,
TradeStatusFilter.waitingInvoice => AppColors.statusWaiting,
TradeStatusFilter.waitingPayment => AppColors.statusWaiting,
TradeStatusFilter.active => AppColors.statusActive,
TradeStatusFilter.fiatSent => AppColors.statusActive,
TradeStatusFilter.success => AppColors.statusSuccess,
Expand Down
29 changes: 29 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:mostro/src/rust/frb_generated.dart';
import 'package:mostro/src/rust/api.dart' as rust_api;
import 'package:mostro/features/settings/providers/nwc_provider.dart';
import 'package:mostro/src/rust/api/nwc.dart' as nwc_api;
import 'package:mostro/src/rust/api/logging.dart' as logging_api;
import 'package:mostro/src/rust/api/nostr.dart' as nostr_api;
import 'package:mostro/src/rust/api/orders.dart' as orders_api;

Expand Down Expand Up @@ -76,6 +77,9 @@ Future<void> main() async {
// Watch for connection state changes in background (logs appear in flutter output).
_watchConnectionState();

// Forward Rust log entries to debugPrint so they appear in `flutter run`.
_forwardRustLogs();

final container = ProviderContainer(
overrides: [
firstRunProvider.overrideWith(
Expand Down Expand Up @@ -125,6 +129,31 @@ void _restoreNwcConnection(String nwcUri, ProviderContainer container) {
});
}

/// Forward Rust log entries to debugPrint so they are visible in `flutter run`.
///
/// Only active in debug builds.
void _forwardRustLogs() {
if (!kDebugMode) return;
debugPrint('[rust-log] starting Rust log forwarder...');
Future.microtask(() async {
try {
debugPrint('[rust-log] subscribing to Rust log stream...');
final stream = await logging_api.onLogEntry();
debugPrint('[rust-log] subscribed — waiting for entries');
while (true) {
final entry = await stream.next();
if (entry == null) {
debugPrint('[rust-log] stream closed');
break;
}
debugPrint('[rust/${entry.tag}] ${entry.message}');
}
} catch (e, st) {
debugPrint('[rust-log] bridge error: $e\n$st');
}
});
}

/// Guards against overlapping diagnostic order polls on rapid reconnects.
bool _isPollingOrders = false;

Expand Down
27 changes: 25 additions & 2 deletions rust/src/api/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,16 @@ pub fn install_log_bridge() {

// Install a custom log::Log that forwards every record.
// max_level is set to Debug so Info/Warn/Error all flow through.
let _ = log::set_logger(&BRIDGE_LOGGER);
log::set_max_level(log::LevelFilter::Debug);
// If a dependency already set a logger, this fails silently —
// the eprintln! fallback in BridgeLogger::log still works, but the
// Flutter stream won't receive entries via the log crate.
match log::set_logger(&BRIDGE_LOGGER) {
Ok(()) => log::set_max_level(log::LevelFilter::Debug),
Err(e) => eprintln!(
"[logging] WARN: set_logger failed ({e}), another logger is already active. \
Using direct bridge for Flutter stream."
),
}
});
}

Expand Down Expand Up @@ -113,6 +121,21 @@ pub(crate) fn forward_log(level: log::Level, target: &str, message: &str) {
}
}

/// Send a log entry directly to the Flutter stream, bypassing the `log` crate.
///
/// Use this when the `log` crate logger may have been hijacked by a dependency.
/// The entry is also printed to stderr for terminal visibility.
pub(crate) fn bridge_log(level: log::Level, tag: &str, message: &str) {
eprintln!("[{level}] {tag}: {message}");
forward_log(level, tag, message);
}

/// Shorthand helpers — always reach both stderr and the Flutter log stream.
pub(crate) fn blog_info(tag: &str, msg: String) { bridge_log(log::Level::Info, tag, &msg); }
pub(crate) fn blog_warn(tag: &str, msg: String) { bridge_log(log::Level::Warn, tag, &msg); }
pub(crate) fn blog_debug(tag: &str, msg: String) { bridge_log(log::Level::Debug, tag, &msg); }


// ── FRB stream ───────────────────────────────────────────────────────────────

/// Stream of log entries for consumption by Flutter.
Expand Down
Loading