@@ -25,6 +25,17 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
2525 // Timer storage for orphan session cleanup
2626 static final Map <String , Timer > _sessionTimeouts = {};
2727
28+ // Tracks orderIds where the user just clicked cancel, so the resulting
29+ // Action.canceled response can be distinguished from a counterparty
30+ // inactivity timeout (which uses the same action).
31+ static final Set <String > _userInitiatedCancels = < String > {};
32+
33+ /// Records that the user just sent a manual cancel for [orderId] .
34+ /// The flag is consumed when the corresponding Action.canceled is processed.
35+ static void markUserInitiatedCancel (String orderId) {
36+ _userInitiatedCancels.add (orderId);
37+ }
38+
2839 AbstractMostroNotifier (
2940 this .orderId,
3041 this .ref, {
@@ -70,6 +81,12 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
7081 // counterparty-specific cancellation reason).
7182 final previousStatus = state.status;
7283
84+ // Consume the user-initiated cancel flag (set by cancelOrder)
85+ // so Action.canceled responses can be distinguished from
86+ // counterparty inactivity timeouts, which use the same action.
87+ final wasUserInitiatedCancel = msg.action == Action .canceled &&
88+ _userInitiatedCancels.remove (orderId);
89+
7390 if (mounted) {
7491 state = state.updateWith (msg);
7592 }
@@ -80,7 +97,9 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
8097 .millisecondsSinceEpoch) {
8198 logger.i (
8299 'Message timestamp check passed, calling handleEvent for ${msg .action }' );
83- unawaited (handleEvent (msg, previousStatus: previousStatus));
100+ unawaited (handleEvent (msg,
101+ previousStatus: previousStatus,
102+ wasUserInitiatedCancel: wasUserInitiatedCancel));
84103 } else {
85104 logger.w (
86105 'Message timestamp check failed for ${msg .action }. Timestamp: ${msg .timestamp }, Current: ${DateTime .now ().millisecondsSinceEpoch }, Threshold: ${DateTime .now ().subtract (const Duration (seconds : 60 )).millisecondsSinceEpoch }' );
@@ -93,7 +112,8 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
93112 'Processing dispute action ${msg .action } despite old timestamp (state update only)' );
94113 unawaited (handleEvent (msg,
95114 bypassTimestampGate: true ,
96- previousStatus: previousStatus));
115+ previousStatus: previousStatus,
116+ wasUserInitiatedCancel: wasUserInitiatedCancel));
97117 }
98118 }
99119 }
@@ -126,7 +146,9 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
126146 }
127147
128148 Future <void > handleEvent (MostroMessage event,
129- {bool bypassTimestampGate = false , Status ? previousStatus}) async {
149+ {bool bypassTimestampGate = false ,
150+ Status ? previousStatus,
151+ bool wasUserInitiatedCancel = false }) async {
130152 // Skip if we've already processed this exact event
131153 final eventKey = '${event .id }_${event .action }_${event .timestamp }' ;
132154 if (_processedEventIds.contains (eventKey)) {
@@ -147,7 +169,9 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
147169 // Extract notification data using the centralized extractor
148170 final notificationData =
149171 await NotificationDataExtractor .extractFromMostroMessage (event, ref,
150- session: session, previousStatus: previousStatus);
172+ session: session,
173+ previousStatus: previousStatus,
174+ wasUserInitiatedCancel: wasUserInitiatedCancel);
151175
152176 // Only notify for recent events; old disputes still update state below
153177 if (notificationData != null && (isRecent || ! bypassTimestampGate)) {
0 commit comments