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
3 changes: 2 additions & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Sat Jun 14 02:07:21 ART 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
192 changes: 163 additions & 29 deletions lib/features/order/models/order_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,78 +82,194 @@ class OrderState {
}

OrderState updateWith(MostroMessage message) {
_logger.i('Updating OrderState Action: ${message.action}');
return copyWith(
status: message.getPayload<Order>()?.status ?? status,
action: message.action != Action.cantDo ? message.action : action,
_logger.i('🔄 Updating OrderState with Action: ${message.action}');

// Preserve the current state entirely for cantDo messages - they are informational only
if (message.action == Action.cantDo) {
return this;
}

// Determine the new status based on the action received
Status newStatus = _getStatusFromAction(
message.action, message.getPayload<Order>()?.status);

// 🔍 DEBUG: Log status mapping
_logger.i('📊 Status mapping: ${message.action} → $newStatus');

// Preserve PaymentRequest correctly
PaymentRequest? newPaymentRequest;
if (message.payload is PaymentRequest) {
newPaymentRequest = message.getPayload<PaymentRequest>();
_logger.i('💳 New PaymentRequest found in message');
} else {
newPaymentRequest = paymentRequest; // Preserve existing
}

final newState = copyWith(
status: newStatus,
action: message.action,
order: message.payload is Order
? message.getPayload<Order>()
: message.payload is PaymentRequest
? message.getPayload<PaymentRequest>()!.order
: order,
paymentRequest: message.getPayload<PaymentRequest>() ?? paymentRequest,
paymentRequest: newPaymentRequest,
cantDo: message.getPayload<CantDo>() ?? cantDo,
dispute: message.getPayload<Dispute>() ?? dispute,
peer: message.getPayload<Peer>() ?? peer,
);

_logger.i('✅ New state: ${newState.status} - ${newState.action}');
_logger
.i('💳 PaymentRequest preserved: ${newState.paymentRequest != null}');

return newState;
}

/// Maps actions to their corresponding statuses based on mostrod DM messages
Status _getStatusFromAction(Action action, Status? payloadStatus) {
switch (action) {
// Actions that should set status to waiting-payment
case Action.waitingSellerToPay:
case Action.payInvoice:
return Status.waitingPayment;

// Actions that should set status to waiting-buyer-invoice
case Action.waitingBuyerInvoice:
case Action.addInvoice:
return Status.waitingBuyerInvoice;

// ✅ FIX: Cuando alguien toma una orden, debe cambiar el status inmediatamente
case Action.takeBuy:
// Cuando buyer toma sell order, seller debe esperar buyer invoice
return Status.waitingBuyerInvoice;

case Action.takeSell:
// Cuando seller toma buy order, seller debe pagar invoice
return Status.waitingPayment;

// Actions that should set status to active
case Action.buyerTookOrder:
case Action.holdInvoicePaymentAccepted:
case Action.holdInvoicePaymentSettled:
return Status.active;

// Actions that should set status to fiat-sent
case Action.fiatSent:
case Action.fiatSentOk:
return Status.fiatSent;

// Actions that should set status to success (completed)
case Action.purchaseCompleted:
case Action.released:
case Action.rate:
case Action.rateReceived:
return Status.success;

// Actions that should set status to canceled
case Action.canceled:
case Action.adminCanceled:
case Action.cooperativeCancelAccepted:
return Status.canceled;

// For actions that include Order payload, use the payload status
case Action.newOrder:
return payloadStatus ?? status;

// For other actions, keep the current status unless payload has a different one
default:
return payloadStatus ?? status;
}
}

List<Action> getActions(Role role) {
return actions[role]![status]![action] ?? [];
return actions[role]?[status]?[action] ?? [];
}

static final Map<Role, Map<Status, Map<Action, List<Action>>>> actions = {
Role.seller: {
Status.pending: {
Action.takeBuy: [
Action.takeBuy,
Action.newOrder: [
Action.cancel,
],
Action.waitingBuyerInvoice: [
Action.takeBuy: [
Action.cancel,
],
},
Status.waitingPayment: {
Action.payInvoice: [
Action.payInvoice,
Action.cancel,
],
Action.newOrder: [
Action.waitingSellerToPay: [
Action.payInvoice,
Action.cancel,
],
},
Status.waitingBuyerInvoice: {
Action.waitingBuyerInvoice: [
Action.cancel,
],
Action.addInvoice: [
Action.cancel,
],
Action.takeBuy: [
Action.cancel,
],
},
Status.active: {
Action.buyerTookOrder: [
Action.buyerTookOrder,
Action.cancel,
Action.dispute,
],
Action.fiatSentOk: [
Action.holdInvoicePaymentAccepted: [
Action.cancel,
Action.dispute,
],
Action.holdInvoicePaymentSettled: [
Action.cancel,
Action.dispute,
],
},
Status.fiatSent: {
Action.fiatSentOk: [
Action.release,
Action.cancel,
Action.dispute,
],
},
Status.success: {
Action.rate: [
Action.rate,
],
Action.purchaseCompleted: [],
Action.holdInvoicePaymentSettled: [],
Action.cooperativeCancelInitiatedByPeer: [
Action.cancel,
Action.purchaseCompleted: [
Action.rate,
],
},
Status.waitingPayment: {
Action.payInvoice: [
Action.payInvoice,
Action.cancel,
Action.released: [
Action.rate,
],
Action.rateReceived: [],
},
Status.canceled: {
Action.canceled: [],
Action.adminCanceled: [],
Action.cooperativeCancelAccepted: [],
},
},
Role.buyer: {
Status.pending: {
Action.newOrder: [
Action.cancel,
],
Action.takeSell: [
Action.takeSell,
Action.cancel,
],
Action.newOrder: [
},
Status.waitingPayment: {
Action.waitingSellerToPay: [
Action.cancel,
],
Action.takeSell: [
Action.cancel,
],
},
Expand All @@ -162,30 +278,48 @@ class OrderState {
Action.addInvoice,
Action.cancel,
],
Action.waitingSellerToPay: [
Action.waitingBuyerInvoice: [
Action.cancel,
],
},
Status.active: {
Action.holdInvoicePaymentAccepted: [
Action.holdInvoicePaymentAccepted,
Action.fiatSent,
Action.cancel,
Action.dispute,
],
Action.holdInvoicePaymentSettled: [
Action.fiatSent,
Action.cancel,
Action.dispute,
],
Action.buyerTookOrder: [
Action.cancel,
Action.dispute,
],
},
Status.fiatSent: {
Action.fiatSentOk: [
Action.cancel,
Action.dispute,
],
},
Status.success: {
Action.rate: [
Action.rate,
],
Action.cooperativeCancelInitiatedByPeer: [
Action.cancel,
Action.purchaseCompleted: [
Action.rate,
],
Action.released: [
Action.rate,
],
Action.rateReceived: [],
Action.purchaseCompleted: [],
Action.paymentFailed: [],
},
Status.canceled: {
Action.canceled: [],
Action.adminCanceled: [],
Action.cooperativeCancelAccepted: [],
},
},
};
Expand Down
11 changes: 8 additions & 3 deletions lib/features/order/notfiers/abstract_mostro_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {

AbstractMostroNotifier(
this.orderId,
this.ref,
) : super(OrderState(
action: Action.newOrder, status: Status.pending, order: null)) {
this.ref, {
OrderState? initialState,
}) : super(initialState ??
OrderState(
action: Action.newOrder,
status: Status.pending,
order: null,
)) {
final oldSession =
ref.read(sessionNotifierProvider.notifier).getSessionByOrderId(orderId);
if (oldSession != null) {
Expand Down
33 changes: 14 additions & 19 deletions lib/features/order/notfiers/order_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,36 @@ import 'package:mostro_mobile/services/mostro_service.dart';

class OrderNotifier extends AbstractMostroNotifier {
late final MostroService mostroService;

OrderNotifier(super.orderId, super.ref) {
mostroService = ref.read(mostroServiceProvider);
sync();
subscribe();
}

Future<void> sync() async {
try {
final storage = ref.read(mostroStorageProvider);
final messages = await storage.getAllMessagesForOrderId(orderId);
if (messages.isEmpty) {
logger.w('No messages found for order $orderId');
return;
}
final msg = messages.firstWhereOrNull((m) => m.action != Action.cantDo);
if (msg?.payload is Order) {
state = OrderState(
status: msg!.getPayload<Order>()!.status,
action: msg.action,
order: msg.getPayload<Order>()!,
);
} else {
final orderMsg =
await storage.getLatestMessageOfTypeById<Order>(orderId);
if (orderMsg != null) {
state = OrderState(
status: orderMsg.getPayload<Order>()!.status,
action: orderMsg.action,
order: orderMsg.getPayload<Order>()!,
);
messages.sort((a, b) {
final timestampA = a.timestamp ?? 0;
final timestampB = b.timestamp ?? 0;
return timestampA.compareTo(timestampB);
});
OrderState currentState = state;
for (final message in messages) {
if (message.action != Action.cantDo) {
currentState = currentState.updateWith(message);
}
}
state = currentState;
logger.i(
'Synced order $orderId to state: ${state.status} - ${state.action}');
} catch (e, stack) {
logger.e(
'Error syncing order state',
'Error syncing order state for $orderId',
error: e,
stackTrace: stack,
);
Expand Down
19 changes: 11 additions & 8 deletions lib/features/trades/providers/trades_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,27 @@ final _logger = Logger();
final filteredTradesProvider = Provider<AsyncValue<List<NostrEvent>>>((ref) {
final allOrdersAsync = ref.watch(orderEventsProvider);
final sessions = ref.watch(sessionNotifierProvider);

_logger.d('Filtering trades: Orders state=${allOrdersAsync.toString().substring(0, math.min(100, allOrdersAsync.toString().length))}, Sessions count=${sessions.length}');


_logger.d(
'Filtering trades: Orders state=${allOrdersAsync.toString().substring(0, math.min(100, allOrdersAsync.toString().length))}, Sessions count=${sessions.length}');

return allOrdersAsync.when(
data: (allOrders) {
final orderIds = sessions.map((s) => s.orderId).toSet();
_logger.d('Got ${allOrders.length} orders and ${orderIds.length} sessions');

_logger
.d('Got ${allOrders.length} orders and ${orderIds.length} sessions');

// Make a copy to avoid modifying the original list
final sortedOrders = List<NostrEvent>.from(allOrders);
sortedOrders.sort((o1, o2) => o1.expirationDate.compareTo(o2.expirationDate));

sortedOrders
.sort((o1, o2) => o1.expirationDate.compareTo(o2.expirationDate));

final filtered = sortedOrders.reversed
.where((o) => orderIds.contains(o.orderId))
.where((o) => o.status != Status.canceled)
.where((o) => o.status != Status.canceledByAdmin)
.toList();

_logger.d('Filtered to ${filtered.length} trades');
return AsyncValue.data(filtered);
},
Expand Down
Loading