Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
68e0ce9
feat: setup l10n and implement to auth wrapper
dewabisma May 19, 2026
56434fc
feat: localize wallet init screen
dewabisma May 19, 2026
f1ea219
feat: onboarding flow localized
dewabisma May 19, 2026
4b3027c
feat: localize home and activity
dewabisma May 19, 2026
57cb374
feat: localize manage account flow
dewabisma May 19, 2026
c46df93
feat: localized send flow
dewabisma May 19, 2026
8a78bf6
feat: localize activity related screen and widgets
dewabisma May 19, 2026
d1c1ef9
feat: localize receive and pos screens
dewabisma May 19, 2026
e920e72
feat: localize setting screens
dewabisma May 19, 2026
c8a86e9
feat: localized swap screens
dewabisma May 19, 2026
d352004
feat: localized shared components
dewabisma May 19, 2026
005fc55
feat: finish integrating language localization
dewabisma May 19, 2026
cdea845
feat: address review issues
dewabisma May 20, 2026
9a47112
feat: another review fixes
dewabisma May 20, 2026
48bb5c9
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 20, 2026
68b8ae3
chore: formatting
dewabisma May 20, 2026
ff5950e
fix: lint error
dewabisma May 20, 2026
9bdcbfb
feat: address reset bug
dewabisma May 20, 2026
72f5aae
feat: remove redundant setting clear
dewabisma May 20, 2026
04306f7
fix: reset depend on setting clear
dewabisma May 20, 2026
e47947a
chore: sync lock file
dewabisma May 20, 2026
8a0ba03
fix: resolve review issues
dewabisma May 20, 2026
a690e03
feat: update activity screen
dewabisma May 20, 2026
2e6bb05
chore: formatting
dewabisma May 20, 2026
d916348
chore: excluce build folder content from flutter analyze
dewabisma May 20, 2026
245b865
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 21, 2026
68df3b7
Merge branch 'beast/language-support' of https://github.com/Quantus-N…
dewabisma May 21, 2026
4d5462c
fix: scroll interefering each filter state
dewabisma May 21, 2026
32767ca
fix: race fetch pull and scroll
dewabisma May 21, 2026
e92bb03
feat: properly debug sensitive information
dewabisma May 21, 2026
aeaad0d
fix: not waiting settings clean up
dewabisma May 21, 2026
e8afa78
feat: resolve PR issues
dewabisma May 21, 2026
b6fb284
Merge branch 'beast/language-support' of https://github.com/Quantus-N…
dewabisma May 21, 2026
02fee5d
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 25, 2026
db3ae6d
feat: make error logged, refactor repeating pagination data access in…
dewabisma May 25, 2026
8699f33
fix: can't remove error in pagination state
dewabisma May 25, 2026
a7a831a
feat: make better empty view in activity screen
dewabisma May 25, 2026
5f4d4fd
fix: PR issues
dewabisma May 25, 2026
ebfbacb
feat: make error clear explicit
dewabisma May 25, 2026
dc6f8aa
feat: remove tx filters
dewabisma May 25, 2026
f283500
chore: formatting
dewabisma May 25, 2026
cafb5f6
feat: handle race fetch condition
dewabisma May 26, 2026
5f2fff0
fix: silent refersh bug
dewabisma May 26, 2026
6b52e33
fix: stuck loading
dewabisma May 26, 2026
c80f16a
fix: properly show loader
dewabisma May 26, 2026
e5df734
chore: formatting
dewabisma May 26, 2026
a84cf86
fix: fetch more not showing loading
dewabisma May 26, 2026
f46603d
fix: stuck loading on failed getting account
dewabisma May 26, 2026
928e9a7
remove dead code cleanup
n13 May 27, 2026
4e21cd4
Some fixes (#500)
dewabisma May 27, 2026
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: 1 addition & 0 deletions mobile-app/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ analyzer:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "lib/generated/**"
- "build/**"
errors:
avoid_print: ignore # print is the most reliable way to debug the app
missing_required_param: error
Expand Down
26 changes: 22 additions & 4 deletions mobile-app/lib/models/pagination_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,49 @@ class PaginationState {
final int otherOffset;
final bool hasMore;
final bool isFetching;
final bool isLoading;
final Object? error;
final StackTrace? stackTrace;

bool get hasLoadedChainData => otherTransfers.isNotEmpty || scheduledReversibleTransfers.isNotEmpty;

PaginationState({
required this.otherTransfers,
required this.scheduledReversibleTransfers,
this.scheduledOffset = 0,
this.otherOffset = 0,
required this.hasMore,
required this.isFetching,
required this.isLoading,
this.error,
this.stackTrace,
});

factory PaginationState.initial() =>
PaginationState(otherTransfers: [], scheduledReversibleTransfers: [], hasMore: true, isFetching: false);
factory PaginationState.initial() => PaginationState(
otherTransfers: [],
scheduledReversibleTransfers: [],
hasMore: true,
isFetching: false,
isLoading: true,
);

/// Returns a copy with the given fields replaced.
///
/// For [error] and [stackTrace]: omitted arguments keep the current values.
/// Pass [error] and/or [stackTrace] to set them. Pass [clearError] true to
/// set both to null; [clearError] takes precedence over [error] and
/// [stackTrace] when all are provided.
PaginationState copyWith({
List<TransactionEvent>? otherTransfers,
List<ReversibleTransferEvent>? scheduledReversibleTransfers,
int? scheduledOffset,
int? otherOffset,
bool? hasMore,
bool? isFetching,
bool? isLoading,
Object? error,
StackTrace? stackTrace,
bool clearError = false,
}) {
return PaginationState(
otherTransfers: otherTransfers ?? this.otherTransfers,
Expand All @@ -42,8 +59,9 @@ class PaginationState {
otherOffset: otherOffset ?? this.otherOffset,
hasMore: hasMore ?? this.hasMore,
isFetching: isFetching ?? this.isFetching,
error: error ?? this.error,
stackTrace: stackTrace ?? this.stackTrace,
isLoading: isLoading ?? this.isLoading,
error: clearError ? null : (error ?? this.error),
stackTrace: clearError ? null : (stackTrace ?? this.stackTrace),
);
}
}
36 changes: 32 additions & 4 deletions mobile-app/lib/providers/active_account_transactions_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,41 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:quantus_sdk/quantus_sdk.dart';
import 'package:resonance_network_wallet/models/combined_transactions_list.dart';
import 'package:resonance_network_wallet/models/filtered_transactions_params.dart';
import 'package:resonance_network_wallet/models/pagination_state.dart';
import 'package:resonance_network_wallet/providers/account_id_list_cache.dart';
import 'package:resonance_network_wallet/providers/account_providers.dart';
import 'package:resonance_network_wallet/providers/controllers/unified_pagination_controller.dart';
import 'package:resonance_network_wallet/providers/filtered_all_transactions_provider.dart';

FilteredTransactionsParams? activeAccountFilteredParams(DisplayAccount? activeAccount, TransactionFilter filter) {
if (activeAccount == null) return null;
return FilteredTransactionsParams(
accountIds: AccountIdListCache.get([activeAccount.account.accountId]),
filter: filter,
);
}

UnifiedPaginationController? readActiveAccountPaginationNotifier(WidgetRef ref, TransactionFilter filter) {
final params = activeAccountFilteredParams(ref.read(activeAccountProvider).value, filter);
if (params == null) return null;
return ref.read(filteredPaginationControllerProviderFamily(params).notifier);
}

/// Pagination state for the active account and [TransactionFilter].
final activeAccountPaginationProvider = Provider.family<PaginationState?, TransactionFilter>((ref, filter) {
final activeAccountValue = ref.watch(activeAccountProvider);

return activeAccountValue.when(
data: (activeAccount) {
final params = activeAccountFilteredParams(activeAccount, filter);
if (params == null) return null;
return ref.watch(filteredPaginationControllerProviderFamily(params));
},
loading: () => null,
error: (_, _) => null,
);
});

/// Provides a filtered list of transactions for the currently active account.
///
/// Parameterised by [TransactionFilter] so callers can independently watch
Expand All @@ -21,10 +52,7 @@ final activeAccountTransactionsProvider = Provider.family<AsyncValue<CombinedTra
if (activeAccount == null) {
return AsyncValue.data(CombinedTransactionsList.empty);
}
final params = FilteredTransactionsParams(
accountIds: AccountIdListCache.get([activeAccount.account.accountId]),
filter: filter,
);
final params = activeAccountFilteredParams(activeAccount, filter)!;
return ref.watch(filteredTransactionsProviderFamily(params));
},
loading: () => const AsyncValue.loading(),
Expand Down
9 changes: 7 additions & 2 deletions mobile-app/lib/providers/all_transactions_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:resonance_network_wallet/models/pagination_state.dart';
import 'package:resonance_network_wallet/providers/controllers/unified_pagination_controller.dart';
import 'package:resonance_network_wallet/providers/pending_cancellations_provider.dart';
import 'package:resonance_network_wallet/providers/pending_transactions_provider.dart';
import 'package:resonance_network_wallet/shared/utils/print.dart';

final paginationControllerProvider = StateNotifierProvider<UnifiedPaginationController, PaginationState>(
(ref) => UnifiedPaginationController(ref),
Expand All @@ -16,10 +17,14 @@ final allTransactionsProvider = Provider<AsyncValue<CombinedTransactionsList>>((
final pending = ref.watch(pendingTransactionsProvider);
final pagination = ref.watch(paginationControllerProvider);

if (pagination.error != null) {
if (pagination.error != null && !pagination.hasLoadedChainData) {
quantusDebugPrint('AllTransactionsProvider: Error: ${pagination.error}');
return AsyncValue.error(pagination.error!, pagination.stackTrace!);
}
if (pagination.isFetching && pagination.otherTransfers.isEmpty) {
if (pagination.error != null) {
quantusDebugPrint('AllTransactionsProvider: Load-more error: ${pagination.error}');
}
if (pagination.isLoading && !pagination.hasLoadedChainData) {
return const AsyncValue.loading();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:resonance_network_wallet/providers/account_id_list_cache.dart';
import 'package:resonance_network_wallet/providers/account_providers.dart';
import 'package:resonance_network_wallet/providers/wallet_providers.dart';
import 'package:resonance_network_wallet/providers/connectivity_provider.dart';
import 'package:resonance_network_wallet/shared/utils/print.dart';

/// Unified pagination controller that handles both all-accounts and
/// filtered-accounts scenarios
Expand Down Expand Up @@ -42,13 +43,13 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
try {
ids = await _getAccountIdsAsync();
} catch (e, st) {
print('Initialization failed: $e\n$st');
state = state.copyWith(error: e, stackTrace: st);
quantusDebugPrint('Initialization failed: $e\n$st');
Comment thread
cursor[bot] marked this conversation as resolved.
state = state.copyWith(error: e, stackTrace: st, isLoading: false);
return;
}

if (ids.isEmpty) {
state = state.copyWith(hasMore: false, isFetching: false);
state = state.copyWith(hasMore: false, isFetching: false, isLoading: false);
return;
}

Expand Down Expand Up @@ -77,7 +78,7 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {

Future<void> _fetchPage(List<String> targetAccountIds) async {
try {
state = state.copyWith(isFetching: true);
state = state.copyWith(isFetching: true, isLoading: true, clearError: true);
final newTransactions = await ref
.read(chainHistoryServiceProvider)
.fetchAllTransactionTypes(
Expand All @@ -98,17 +99,17 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
scheduledOffset: newTransactions.nextScheduledOffset,
hasMore: newTransactions.hasMore,
isFetching: false,
error: null,
stackTrace: null,
isLoading: false,
clearError: true,
);
} catch (e, st) {
print('Fetch page failed: $e\n$st');
state = state.copyWith(error: e, stackTrace: st, isFetching: false);
quantusDebugPrint('Fetch page failed: $e\n$st');
state = state.copyWith(error: e, stackTrace: st, isFetching: false, isLoading: false);
}
}

Future<void> fetchMore() async {
print('UnifiedPaginationController: Fetch more');
quantusDebugPrint('UnifiedPaginationController: Fetch more');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overlapping fetchMore requests

Medium Severity

fetchMore only sets isFetching inside _fetchPage, so two calls can pass the guard at once and issue the same paged request. Offsets can advance twice while the UI dedupes by id, which can skip or reorder history pages.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f283500. Configure here.


if (state.isFetching || !state.hasMore) return;

Expand All @@ -121,30 +122,36 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
/// Refresh data silently without showing loading indicators.
/// Used for automatic polling to update data in background.
Future<void> silentRefresh() async {
print('UnifiedPaginationController: Silent refresh called');
quantusDebugPrint('UnifiedPaginationController: Silent refresh called');
if (state.isFetching) return;

final targetAccountIds = _getAccountIds();
if (targetAccountIds.isEmpty) return;

await _silentFetchFirstPage(targetAccountIds);
state = state.copyWith(isFetching: true);

try {
await _silentFetchFirstPage(targetAccountIds);
} finally {
state = state.copyWith(isFetching: false);
}
}

/// Refresh data with loading indicators.
/// Used for user-initiated refreshes like pull-to-refresh.
Future<void> loadingRefresh() async {
print('UnifiedPaginationController: Loading Refresh');
quantusDebugPrint('UnifiedPaginationController: Loading Refresh');

// Check connectivity before refreshing
final isOnline = ref.read(isOnlineProvider);
if (!isOnline) {
print('Skipping refresh - offline');
quantusDebugPrint('Skipping refresh - offline');
Comment thread
cursor[bot] marked this conversation as resolved.
return;
}

final targetAccountIds = _getAccountIds();
if (targetAccountIds.isEmpty) {
state = PaginationState.initial().copyWith(hasMore: false);
state = PaginationState.initial().copyWith(hasMore: false, isLoading: false);
return;
}

Expand All @@ -168,27 +175,26 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
otherOffset: newTransactions.nextOtherOffset,
scheduledOffset: newTransactions.nextScheduledOffset,
hasMore: newTransactions.hasMore,
error: null,
stackTrace: null,
clearError: true,
);
} catch (e, st) {
print('Silent refresh failed: $e, $st');
Comment thread
cursor[bot] marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent refresh hides PTR errors

Medium Severity

When pull-to-refresh uses silentRefresh, failures in _silentFetchFirstPage are only logged; state keeps prior data and no error, so RefreshIndicator still succeeds and the user is not informed the refresh failed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f283500. Configure here.

quantusDebugPrint('Silent refresh failed: $e, $st');
}
}

/// Update a reversible transfer status to executed inline without full
/// refresh.
/// Moves the transfer from reversibleTransfers to the top of items list.
void updateReversibleTransferToExecuted(String txId, ReversibleTransferStatus newStatus) {
print('Updating reversible transfer to executed: $txId');
quantusDebugPrint('Updating reversible transfer to executed: $txId');

// Find the reversible transfer with the matching hash
final reversibleTransfer = state.scheduledReversibleTransfers
.where((transfer) => transfer.txId == txId)
.firstOrNull;

if (reversibleTransfer == null) {
print('Reversible transfer not found for txId: $txId');
quantusDebugPrint('Reversible transfer not found for txId: $txId');
return;
}

Expand Down Expand Up @@ -221,13 +227,13 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
scheduledReversibleTransfers: updatedScheduledReversibleTransfers,
);

print('Successfully moved transfer from reversible to executed');
quantusDebugPrint('Successfully moved transfer from reversible to executed');
}

/// Adds a newly found transaction to the top of the history list.
/// This is used when a broadcast transaction is found in blockchain history.
void addTransactionToHistory(TransactionEvent transaction) {
print('Adding transaction to history: ${transaction.id}');
quantusDebugPrint('Adding transaction to history: ${transaction.id}');

// Check if transaction already exists to avoid duplicates
final existsInOtherTransfers = state.otherTransfers.any((item) => item.id == transaction.id);
Expand All @@ -236,7 +242,7 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
);

if (existsInOtherTransfers || existsInScheduledReversibleTransfers) {
print('Transaction ${transaction.id} already exists in history');
quantusDebugPrint('Transaction ${transaction.id} already exists in history');
return;
}

Expand All @@ -250,6 +256,6 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
state = state.copyWith(otherTransfers: updatedOtherTransfers);
}

print('Successfully added transaction ${transaction.id} to history');
quantusDebugPrint('Successfully added transaction ${transaction.id} to history');
}
}
10 changes: 7 additions & 3 deletions mobile-app/lib/providers/filtered_all_transactions_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:resonance_network_wallet/providers/account_id_list_cache.dart';
import 'package:resonance_network_wallet/providers/controllers/unified_pagination_controller.dart';
import 'package:resonance_network_wallet/providers/pending_cancellations_provider.dart';
import 'package:resonance_network_wallet/providers/pending_transactions_provider.dart';
import 'package:resonance_network_wallet/shared/utils/print.dart';

/// Family provider for filtered pagination controllers.
///
Expand All @@ -31,11 +32,14 @@ final filteredTransactionsProviderFamily =
final pending = ref.watch(pendingTransactionsProvider);
final pagination = ref.watch(filteredPaginationControllerProviderFamily(normalizedParams));

if (pagination.error != null) {
print('FilteredTransactionsProvider: Error: ${pagination.error}');
if (pagination.error != null && !pagination.hasLoadedChainData) {
quantusDebugPrint('FilteredTransactionsProvider: Error: ${pagination.error}');
return AsyncValue.error(pagination.error!, pagination.stackTrace!);
}
if (pagination.isFetching && pagination.otherTransfers.isEmpty) {
if (pagination.error != null) {
quantusDebugPrint('FilteredTransactionsProvider: Load-more error: ${pagination.error}');
}
if (pagination.isLoading && !pagination.hasLoadedChainData) {
return const AsyncValue.loading();
}

Expand Down
5 changes: 2 additions & 3 deletions mobile-app/lib/v2/screens/accounts/create_account_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ class _CreateAccountScreenState extends ConsumerState<CreateAccountScreen> {
List<Account> _accounts = [];
int _walletIndex = 0;
bool _isLoading = false;
String? _error;

bool get _isDisabled => _accountName.text.isEmpty || _isLoading || _error != null;
bool get _isDisabled => _accountName.text.trim().isEmpty || _isLoading;

int _walletIndexForActiveAccount(List<Account> accounts, DisplayAccount? activeDisplayAccount) {
if (activeDisplayAccount is RegularAccount) {
Expand Down Expand Up @@ -126,7 +125,7 @@ class _CreateAccountScreenState extends ConsumerState<CreateAccountScreen> {

return ScaffoldBase(
appBar: V2AppBar(title: l10n.createAccountAppBarTitle),
mainContent: NameField(controller: _accountName, subtitle: l10n.createAccountSubtitle, error: _error),
mainContent: NameField(controller: _accountName, subtitle: l10n.createAccountSubtitle),
bottomContent: ScaffoldBaseBottomContent(
child: QuantusButton.simple(
label: l10n.createAccountButton,
Expand Down
3 changes: 3 additions & 0 deletions mobile-app/lib/v2/screens/accounts/edit_account_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class EditAccountScreenState extends ConsumerState<EditAccountScreen> {
final _accountsService = AccountsService();
bool _saving = false;

bool get _isDisabled => _controller.text.trim().isEmpty;

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -82,6 +84,7 @@ class EditAccountScreenState extends ConsumerState<EditAccountScreen> {
label: l10n.editAccountDone,
onTap: _save,
isLoading: _saving,
isDisabled: _isDisabled,
),
),
);
Expand Down
Loading
Loading