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
6 changes: 5 additions & 1 deletion lib/data/models/last_trade_index_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import 'package:mostro_mobile/data/models/payload.dart';

class LastTradeIndexResponse implements Payload {
final int tradeIndex;
final bool noHistoryFound;

const LastTradeIndexResponse({required this.tradeIndex});
const LastTradeIndexResponse({
required this.tradeIndex,
this.noHistoryFound = false,
});

@override
String get type => 'last-trade-index';
Expand Down
64 changes: 56 additions & 8 deletions lib/features/restore/restore_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mostro_mobile/services/logger_service.dart';
import 'package:mostro_mobile/features/mostro/mostro_instance.dart';
import 'package:mostro_mobile/data/models/enums/action.dart';
import 'package:mostro_mobile/data/models/enums/cant_do_reason.dart';
import 'package:mostro_mobile/data/models/enums/role.dart';
import 'package:mostro_mobile/data/models/enums/order_type.dart';
import 'package:mostro_mobile/data/models/enums/status.dart';
Expand Down Expand Up @@ -439,12 +440,27 @@ class RestoreService {
final contentList = jsonDecode(rumor.content!) as List<dynamic>;
final messageData = contentList[0] as Map<String, dynamic>;

// Check if Mostro returned cant-do (not found)
// Check if Mostro returned cant-do
if (messageData.containsKey('cant-do')) {
logger.w(
'Restore: Mostro returned cant-do for last trade index, defaulting to 0',
final cantDoWrapper = messageData['cant-do'] as Map<String, dynamic>?;
final cantDoPayload = cantDoWrapper?['payload'] as Map<String, dynamic>?;
final reasonStr = _extractCantDoReason(cantDoPayload);
if (reasonStr == CantDoReason.invalidTradeIndex.value) {
logger.w(
'Restore: Mostro returned cant-do: invalid_trade_index for last trade index',
);
throw const RestoreInvalidTradeIndexException();
}
if (reasonStr == CantDoReason.notFound.value) {
logger.w('Restore: no previous trade history found');
return const LastTradeIndexResponse(
tradeIndex: 0,
noHistoryFound: true,
);
}
throw Exception(
'Restore: cant-do on last trade index (reason: ${reasonStr ?? 'unknown'})',
);
return LastTradeIndexResponse(tradeIndex: 0);
}

// Extract trade_index from restore wrapper
Expand Down Expand Up @@ -840,6 +856,7 @@ class RestoreService {
_operationInProgress = true;
_operationCompleter = Completer<bool>();
bool success = false;
bool noHistoryFound = false;
try {
// Clear existing data
await _clearAll();
Expand Down Expand Up @@ -895,6 +912,7 @@ class RestoreService {
);
final lastTradeIndex = lastTradeIndexResponse.tradeIndex;
await keyManager.setCurrentKeyIndex(lastTradeIndex + 1);
noHistoryFound = lastTradeIndexResponse.noHistoryFound;
success = true;
return true;
}
Expand All @@ -920,6 +938,7 @@ class RestoreService {
lastTradeIndexEvent,
);
final lastTradeIndex = lastTradeIndexResponse.tradeIndex;
noHistoryFound = lastTradeIndexResponse.noHistoryFound;

// IMPORTANT: Cancel temporary subscription before proceeding to avoid interference
await _tempSubscription?.cancel();
Expand All @@ -944,11 +963,12 @@ class RestoreService {
error: e,
stackTrace: stack,
);
final errorKey = e is RestoreInvalidTradeIndexException
? 'invalid_trade_index'
: 'restore_error';
ref
.read(restoreProgressProvider.notifier)
.showError(
'restore_error',
); // overlay displays localized restoreErrorMessage
.showError(errorKey); // overlay maps the key to a localized message
} finally {
// Cleanup: always cancel subscription and clear keys
logger.i('Restore: cleaning up subscription and keys');
Expand All @@ -964,7 +984,11 @@ class RestoreService {
// Only call completeRestore if not in error state
final currentState = ref.read(restoreProgressProvider);
if (currentState.step != RestoreStep.error) {
ref.read(restoreProgressProvider.notifier).completeRestore();
if (noHistoryFound) {
ref.read(restoreProgressProvider.notifier).completeAsNewUser();
} else {
ref.read(restoreProgressProvider.notifier).completeRestore();
}
}
}

Expand Down Expand Up @@ -1027,6 +1051,30 @@ class RestoreService {
_operationCompleter = null;
}
}

// Extracts the cant-do reason string from the inner payload.
// Mostro wire format: payload may be {"cant_do": "<reason>"} or
// {"cant_do": {"cant-do": "<reason>"}}.
String? _extractCantDoReason(Map<String, dynamic>? payload) {
if (payload == null) return null;
final raw = payload['cant_do'];
if (raw == null) return null;
if (raw is String) return raw;
if (raw is Map<String, dynamic>) {
final inner = raw['cant-do'];
return inner?.toString();
}
return null;
}
}

/// Thrown when Mostro responds with cant-do: invalid_trade_index to
/// Action.lastTradeIndex during the restore flow.
class RestoreInvalidTradeIndexException implements Exception {
const RestoreInvalidTradeIndexException();

@override
String toString() => 'RestoreInvalidTradeIndexException';
}

final restoreServiceProvider = Provider<RestoreService>((ref) {
Expand Down
26 changes: 26 additions & 0 deletions lib/features/restore/restore_overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,32 @@ class RestoreOverlay extends ConsumerWidget {
),
textAlign: TextAlign.center,
),
if (state.step == RestoreStep.completed &&
state.noHistoryFound) ...[
SizedBox(height: isSmallScreen ? 6 : 8),
Text(
S.of(context)!.restoreNoPreviousData,
style: TextStyle(
color: AppTheme.textSecondary.withValues(alpha: 0.75),
fontSize: messageFontSize - 1,
fontStyle: FontStyle.italic,
),
textAlign: TextAlign.center,
),
],
if (state.step == RestoreStep.error &&
state.errorMessage == 'invalid_trade_index') ...[
SizedBox(height: isSmallScreen ? 6 : 8),
Text(
S.of(context)!.restoreInvalidTradeIndex,
style: TextStyle(
color: AppTheme.textSecondary.withValues(alpha: 0.75),
fontSize: messageFontSize - 1,
fontStyle: FontStyle.italic,
),
textAlign: TextAlign.center,
),
],
SizedBox(height: isSmallScreen ? 16 : 24),
_buildProgressIndicator(state, iconSize),
],
Expand Down
20 changes: 20 additions & 0 deletions lib/features/restore/restore_progress_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,26 @@ class RestoreProgressNotifier extends StateNotifier<RestoreProgressState> {
});
}

void completeAsNewUser() {
if (!_canUpdateState || state.step == RestoreStep.completed) return;

logger.i('Restore completed: no previous history for this Mostro');
_cancelTimeoutTimer();

_setStateSafely(state.copyWith(
step: RestoreStep.completed,
noHistoryFound: true,
));

// Extended auto-hide so the user has time to read the secondary line.
_cancelAutoHideTimer();
_autoHideTimer = Timer(const Duration(seconds: 5), () {
if (mounted) {
hide();
}
});
}

void showError(String message) {
if (!_canUpdateState || state.step == RestoreStep.error) return;

Expand Down
4 changes: 4 additions & 0 deletions lib/features/restore/restore_progress_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ class RestoreProgressState {
final int totalProgress;
final String? errorMessage;
final bool isVisible;
final bool noHistoryFound;

const RestoreProgressState({
required this.step,
this.currentProgress = 0,
this.totalProgress = 0,
this.errorMessage,
this.isVisible = false,
this.noHistoryFound = false,
});

RestoreProgressState copyWith({
Expand All @@ -29,13 +31,15 @@ class RestoreProgressState {
int? totalProgress,
String? errorMessage,
bool? isVisible,
bool? noHistoryFound,
}) {
return RestoreProgressState(
step: step ?? this.step,
currentProgress: currentProgress ?? this.currentProgress,
totalProgress: totalProgress ?? this.totalProgress,
errorMessage: errorMessage ?? this.errorMessage,
isVisible: isVisible ?? this.isVisible,
noHistoryFound: noHistoryFound ?? this.noHistoryFound,
);
}

Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/intl_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1243,8 +1243,10 @@
"restoreProcessingRoles": "Rollen werden verarbeitet...",
"restoreFinalizing": "Wiederherstellung wird abgeschlossen...",
"restoreCompleted": "Wiederherstellung abgeschlossen!",
"restoreNoPreviousData": "Keine vorherigen Daten für diesen Nutzer in diesem Mostro",
"restoreError": "Wiederherstellungsfehler",
"restoreErrorMessage": "Fehler beim Wiederherstellen der Nutzerdaten. Bitte prüfe deine Verbindung und versuche es erneut.",
"restoreInvalidTradeIndex": "Ungültiger Handelsindex",
"@_comment_file_messaging": "Dateiversand-Strings",
"download": "Herunterladen",
"openFile": "Datei öffnen",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1243,8 +1243,10 @@
"restoreProcessingRoles": "Processing roles...",
"restoreFinalizing": "Finalizing restore...",
"restoreCompleted": "Restore completed!",
"restoreNoPreviousData": "No previous data for this user in this Mostro",
"restoreError": "Restore error",
"restoreErrorMessage": "Error restoring user data. Please check your connection and try again.",
"restoreInvalidTradeIndex": "Invalid trade index",
"@_comment_file_messaging": "File messaging strings",
"download": "Download",
"openFile": "Open File",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/intl_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1218,8 +1218,10 @@
"restoreProcessingRoles": "Procesando roles...",
"restoreFinalizing": "Finalizando restauración...",
"restoreCompleted": "¡Restauración completada!",
"restoreNoPreviousData": "No hay datos previos de este usuario en este Mostro",
"restoreError": "Error de restauración",
"restoreErrorMessage": "Error al restaurar datos del usuario. Verifica tu conexión e inténtalo más tarde.",
"restoreInvalidTradeIndex": "Índice de intercambio no válido",
"@_comment_file_messaging": "File messaging strings",
"download": "Descargar",
"openFile": "Abrir Archivo",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/intl_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1243,8 +1243,10 @@
"restoreProcessingRoles": "Traitement des rôles...",
"restoreFinalizing": "Finalisation de la restauration...",
"restoreCompleted": "Restauration terminée !",
"restoreNoPreviousData": "Aucune donnée précédente pour cet utilisateur sur ce Mostro",
"restoreError": "Erreur de restauration",
"restoreErrorMessage": "Erreur lors de la restauration des données utilisateur. Veuillez vérifier votre connexion et réessayer.",
"restoreInvalidTradeIndex": "Index d'échange invalide",
"@_comment_file_messaging": "File messaging strings",
"download": "Télécharger",
"openFile": "Ouvrir le fichier",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/intl_it.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1284,8 +1284,10 @@
"restoreProcessingRoles": "Elaborazione ruoli...",
"restoreFinalizing": "Finalizzazione ripristino...",
"restoreCompleted": "Ripristino completato!",
"restoreNoPreviousData": "Nessun dato precedente per questo utente in questo Mostro",
"restoreError": "Errore di ripristino",
"restoreErrorMessage": "Errore nel ripristino dei dati utente. Verifica la tua connessione e riprova più tardi.",
"restoreInvalidTradeIndex": "Indice di scambio non valido",
"@_comment_file_messaging": "File messaging strings",
"download": "Scarica",
"openFile": "Apri File",
Expand Down
Loading