diff --git a/mobile/lib/core/ui/refresh_feedback.dart b/mobile/lib/core/ui/refresh_feedback.dart new file mode 100644 index 0000000..d1c63cc --- /dev/null +++ b/mobile/lib/core/ui/refresh_feedback.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +import '../../l10n/app_localizations.dart'; + +Future runRefreshWithFeedback( + BuildContext context, + Future Function() refresh, +) async { + try { + await refresh(); + } catch (error) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of( + context, + ).trf('common.refreshFailed', [error.toString()]), + ), + behavior: SnackBarBehavior.floating, + ), + ); + } +} diff --git a/mobile/lib/features/auth/login_page.dart b/mobile/lib/features/auth/login_page.dart index d66a915..a5da8d8 100644 --- a/mobile/lib/features/auth/login_page.dart +++ b/mobile/lib/features/auth/login_page.dart @@ -43,7 +43,6 @@ class _LoginPageState extends State { LoginExpiry _expiry = LoginExpiry.currentDay; bool _savePassword = false; bool _httpRiskAccepted = false; - bool _showHttpRiskBanner = false; bool _submitting = false; String? _errorMessage; @@ -108,7 +107,11 @@ class _LoginPageState extends State { if (!mounted) return; if (result.requiresHttpRiskConfirmation) { - setState(() => _showHttpRiskBanner = true); + setState(() => _submitting = false); + final accepted = await _confirmHttpRisk(); + if (!mounted || accepted != true) return; + setState(() => _httpRiskAccepted = true); + await _submit(); return; } if (!result.success || result.session == null) { @@ -121,12 +124,25 @@ class _LoginPageState extends State { } } - Future _acceptHttpRiskAndSubmit() async { - setState(() { - _httpRiskAccepted = true; - _showHttpRiskBanner = false; - }); - await _submit(); + Future _confirmHttpRisk() { + final l = AppLocalizations.of(context); + return showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: Text(l.tr('login.httpRiskTitle')), + content: Text(l.tr('login.httpRiskBody')), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(false), + child: Text(l.tr('common.cancel')), + ), + FilledButton( + onPressed: () => Navigator.of(dialogContext).pop(true), + child: Text(l.tr('common.continue')), + ), + ], + ), + ); } @override @@ -170,10 +186,6 @@ class _LoginPageState extends State { onChanged: (value) => setState(() => _savePassword = value), ), - if (_showHttpRiskBanner) ...[ - const SizedBox(height: 14), - _HttpRiskBanner(onConfirm: _acceptHttpRiskAndSubmit), - ], if (_errorMessage != null) ...[ const SizedBox(height: 14), _ErrorBox(message: _errorMessage!), @@ -490,65 +502,6 @@ class _LoginHero extends ConsumerWidget { } } -class _HttpRiskBanner extends StatelessWidget { - const _HttpRiskBanner({required this.onConfirm}); - - final VoidCallback onConfirm; - - @override - Widget build(BuildContext context) { - final l = AppLocalizations.of(context); - return Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: const Color(0xFFFFE8BF), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE6A23C)), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon(Icons.warning_amber, color: Color(0xFF7A4A00), size: 20), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l.tr('login.httpRiskTitle'), - style: const TextStyle( - color: Color(0xFF7A4A00), - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 4), - Text( - l.tr('login.httpRiskBody'), - style: const TextStyle( - color: Color(0xFF7A4A00), - fontSize: 13, - height: 1.35, - ), - ), - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: onConfirm, - style: TextButton.styleFrom( - foregroundColor: const Color(0xFF7A4A00), - ), - child: Text(l.tr('common.continue')), - ), - ), - ], - ), - ), - ], - ), - ); - } -} - class _ErrorBox extends StatelessWidget { const _ErrorBox({required this.message}); diff --git a/mobile/lib/features/servers/servers_tab.dart b/mobile/lib/features/servers/servers_tab.dart index fa19c89..29cb0a7 100644 --- a/mobile/lib/features/servers/servers_tab.dart +++ b/mobile/lib/features/servers/servers_tab.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../../core/api/api_result.dart'; import '../../core/ui/palette.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../l10n/app_localizations.dart'; import '../../state/api_providers.dart'; import '../../state/auth_notifier.dart'; @@ -55,7 +56,7 @@ class _ServersTabState extends ConsumerState { } Future _refresh() async { - await refreshServerSharedData(ref); + await runRefreshWithFeedback(context, () => refreshServerSharedData(ref)); } Future _connect(ServerModel server) async { diff --git a/mobile/lib/features/settings/credentials_page.dart b/mobile/lib/features/settings/credentials_page.dart index e4aa466..d32b224 100644 --- a/mobile/lib/features/settings/credentials_page.dart +++ b/mobile/lib/features/settings/credentials_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_result.dart'; import '../../core/ui/palette.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../l10n/app_localizations.dart'; import '../../state/api_providers.dart'; import '../../state/credential_list_notifier.dart'; @@ -206,8 +207,12 @@ class _CredentialsPageState extends ConsumerState { ), Expanded( child: RefreshIndicator( - onRefresh: () => - ref.read(credentialListProvider.notifier).refresh(), + onRefresh: () => runRefreshWithFeedback( + context, + () => ref + .read(credentialListProvider.notifier) + .refresh(throwOnError: true), + ), child: async.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => _ErrorBody( diff --git a/mobile/lib/features/settings/plus_subscription_page.dart b/mobile/lib/features/settings/plus_subscription_page.dart index d73c361..7d8434f 100644 --- a/mobile/lib/features/settings/plus_subscription_page.dart +++ b/mobile/lib/features/settings/plus_subscription_page.dart @@ -5,6 +5,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../core/api/api_result.dart'; import '../../core/ui/palette.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../l10n/app_localizations.dart'; import '../../state/api_providers.dart'; import '../../state/plus_info_notifier.dart'; @@ -127,10 +128,12 @@ class _PlusSubscriptionPageState extends ConsumerState { ], ), body: RefreshIndicator( - onRefresh: () async { - await ref.read(plusInfoProvider.notifier).refresh(); + onRefresh: () => runRefreshWithFeedback(context, () async { + await ref + .read(plusInfoProvider.notifier) + .refresh(throwOnError: true); await _loadInitial(); - }, + }), child: plusAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => _ErrorBody( diff --git a/mobile/lib/features/settings/proxy_page.dart b/mobile/lib/features/settings/proxy_page.dart index 023e972..19c0eb4 100644 --- a/mobile/lib/features/settings/proxy_page.dart +++ b/mobile/lib/features/settings/proxy_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_result.dart'; import '../../core/ui/palette.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../l10n/app_localizations.dart'; import '../../state/api_providers.dart'; import '../../state/plus_info_notifier.dart'; @@ -247,7 +248,12 @@ class _ProxyPageState extends ConsumerState { ), Expanded( child: RefreshIndicator( - onRefresh: () => ref.read(proxyListProvider.notifier).refresh(), + onRefresh: () => runRefreshWithFeedback( + context, + () => ref + .read(proxyListProvider.notifier) + .refresh(throwOnError: true), + ), child: async.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => _ErrorBody( diff --git a/mobile/lib/features/settings/sessions_page.dart b/mobile/lib/features/settings/sessions_page.dart index f86f2bb..0e6ff99 100644 --- a/mobile/lib/features/settings/sessions_page.dart +++ b/mobile/lib/features/settings/sessions_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_result.dart'; import '../../core/ui/palette.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../l10n/app_localizations.dart'; import '../../state/api_providers.dart'; import '../../state/auth_notifier.dart'; @@ -212,7 +213,11 @@ class _SessionsPageState extends ConsumerState { ], ), body: RefreshIndicator( - onRefresh: () => ref.read(loginLogProvider.notifier).refresh(), + onRefresh: () => runRefreshWithFeedback( + context, + () => + ref.read(loginLogProvider.notifier).refresh(throwOnError: true), + ), child: logAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => _ErrorBody( diff --git a/mobile/lib/features/shell/scripts_tab.dart b/mobile/lib/features/shell/scripts_tab.dart index 101df3d..7437b66 100644 --- a/mobile/lib/features/shell/scripts_tab.dart +++ b/mobile/lib/features/shell/scripts_tab.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_result.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../features/scripts/script_form_page.dart'; import '../../features/scripts/script_group_model.dart'; import '../../features/scripts/script_groups_page.dart'; @@ -39,10 +40,15 @@ class _ScriptsTabState extends ConsumerState { } Future _refresh() async { - await Future.wait([ - ref.read(scriptListProvider.notifier).refresh(), - ref.read(scriptGroupListProvider.notifier).refresh(), - ]); + await runRefreshWithFeedback( + context, + () => Future.wait([ + ref.read(scriptListProvider.notifier).refresh(throwOnError: true), + ref + .read(scriptGroupListProvider.notifier) + .refresh(throwOnError: true), + ]), + ); } Future _openForm({ScriptModel? script}) async { diff --git a/mobile/lib/features/shell/settings_tab.dart b/mobile/lib/features/shell/settings_tab.dart index 3e24071..230a816 100644 --- a/mobile/lib/features/shell/settings_tab.dart +++ b/mobile/lib/features/shell/settings_tab.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/ui/palette.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../l10n/app_localizations.dart'; import '../../state/auth_notifier.dart'; import '../../state/credential_list_notifier.dart'; @@ -184,14 +185,14 @@ class SettingsTab extends ConsumerWidget { ); } - Future _refresh(WidgetRef ref) async { - await Future.wait([ - ref.read(hostListProvider.notifier).refresh(), - ref.read(credentialListProvider.notifier).refresh(), - ref.read(scriptListProvider.notifier).refresh(), - ref.read(plusInfoProvider.notifier).refresh(), - ref.read(plusDiscountProvider.notifier).refresh(), - ]); + Future _refresh(BuildContext context, WidgetRef ref) async { + await runRefreshWithFeedback(context, () => Future.wait([ + ref.read(hostListProvider.notifier).refresh(throwOnError: true), + ref.read(credentialListProvider.notifier).refresh(throwOnError: true), + ref.read(scriptListProvider.notifier).refresh(throwOnError: true), + ref.read(plusInfoProvider.notifier).refresh(throwOnError: true), + ref.read(plusDiscountProvider.notifier).refresh(throwOnError: true), + ])); } @override @@ -218,7 +219,7 @@ class SettingsTab extends ConsumerWidget { body: SafeArea( bottom: false, child: RefreshIndicator( - onRefresh: () => _refresh(ref), + onRefresh: () => _refresh(context, ref), child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ diff --git a/mobile/lib/features/shell/sftp_tab.dart b/mobile/lib/features/shell/sftp_tab.dart index 5e0301b..c8fa188 100644 --- a/mobile/lib/features/shell/sftp_tab.dart +++ b/mobile/lib/features/shell/sftp_tab.dart @@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import '../../core/api/api_result.dart'; +import '../../core/ui/refresh_feedback.dart'; import '../../features/servers/server_model.dart'; import '../../l10n/app_localizations.dart'; import '../../state/api_providers.dart'; @@ -30,7 +31,10 @@ class SftpTab extends ConsumerStatefulWidget { class _SftpTabState extends ConsumerState { bool _connecting = false; - Future _refresh() => ref.read(hostListProvider.notifier).refresh(); + Future _refresh() => runRefreshWithFeedback( + context, + () => ref.read(hostListProvider.notifier).refresh(throwOnError: true), + ); Future _openServerPicker() async { final manager = ref.read(sftpSessionManagerProvider); diff --git a/mobile/lib/l10n/strings_en.dart b/mobile/lib/l10n/strings_en.dart index 96e528b..01a5093 100644 --- a/mobile/lib/l10n/strings_en.dart +++ b/mobile/lib/l10n/strings_en.dart @@ -24,6 +24,7 @@ const Map stringsEn = { 'common.exitApp': 'Exit', 'common.exitAppTitle': 'Exit app?', 'common.exitAppBody': 'Are you sure you want to exit the app?', + 'common.refreshFailed': 'Refresh failed: {0}', // Login page 'login.serverAddress': 'Server address', diff --git a/mobile/lib/l10n/strings_zh.dart b/mobile/lib/l10n/strings_zh.dart index c6e10b7..a96c9c9 100644 --- a/mobile/lib/l10n/strings_zh.dart +++ b/mobile/lib/l10n/strings_zh.dart @@ -24,6 +24,8 @@ const Map stringsZh = { 'common.exitAppTitle': '退出 App?', 'common.exitAppBody': '确认退出 App?', + 'common.refreshFailed': '刷新失败:{0}', + // Login 'login.serverAddress': '服务端地址', 'login.serverAddressHint': 'https://example.com', diff --git a/mobile/lib/state/credential_list_notifier.dart b/mobile/lib/state/credential_list_notifier.dart index 659f892..80802d1 100644 --- a/mobile/lib/state/credential_list_notifier.dart +++ b/mobile/lib/state/credential_list_notifier.dart @@ -13,13 +13,18 @@ class CredentialListNotifier return _fetch(ref.watch(apiClientProvider)); } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) state = const AsyncLoading(); - state = await AsyncValue.guard( - () => _fetch(ref.read(apiClientProvider)), - ); - if (state.hasError && previous != null) state = AsyncData(previous); + try { + state = AsyncData(await _fetch(ref.read(apiClientProvider))); + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; + } } Future> _fetch(ApiClient api) async { diff --git a/mobile/lib/state/group_list_notifier.dart b/mobile/lib/state/group_list_notifier.dart index 9ff6309..e430467 100644 --- a/mobile/lib/state/group_list_notifier.dart +++ b/mobile/lib/state/group_list_notifier.dart @@ -18,21 +18,24 @@ class GroupListNotifier extends AsyncNotifier> { } } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) { state = const AsyncLoading(); } - state = await AsyncValue.guard(() async { - try { - return await ref.read(serverRepositoryProvider).fetchGroups(); - } on UnauthorizedFailure { - await ref.read(authProvider.notifier).signOut(); - rethrow; - } - }); - if (state.hasError && previous != null) { - state = AsyncData(previous); + try { + final groups = await ref.read(serverRepositoryProvider).fetchGroups(); + state = AsyncData(groups); + } on UnauthorizedFailure { + await ref.read(authProvider.notifier).signOut(); + if (!throwOnError) return; + rethrow; + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; } } } diff --git a/mobile/lib/state/host_list_notifier.dart b/mobile/lib/state/host_list_notifier.dart index 36bc2cb..e2ff3a2 100644 --- a/mobile/lib/state/host_list_notifier.dart +++ b/mobile/lib/state/host_list_notifier.dart @@ -20,21 +20,24 @@ class HostListNotifier extends AsyncNotifier> { } } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) { state = const AsyncLoading(); } - state = await AsyncValue.guard(() async { - try { - return await ref.read(serverRepositoryProvider).fetchHosts(); - } on UnauthorizedFailure { - await ref.read(authProvider.notifier).signOut(); - rethrow; - } - }); - if (state.hasError && previous != null) { - state = AsyncData(previous); + try { + final hosts = await ref.read(serverRepositoryProvider).fetchHosts(); + state = AsyncData(hosts); + } on UnauthorizedFailure { + await ref.read(authProvider.notifier).signOut(); + if (!throwOnError) return; + rethrow; + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; } } } diff --git a/mobile/lib/state/login_log_notifier.dart b/mobile/lib/state/login_log_notifier.dart index 6d4e875..fe07cc2 100644 --- a/mobile/lib/state/login_log_notifier.dart +++ b/mobile/lib/state/login_log_notifier.dart @@ -16,18 +16,23 @@ class LoginLogNotifier extends AsyncNotifier { } } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) state = const AsyncLoading(); - state = await AsyncValue.guard(() async { - try { - return await ref.read(settingsRepositoryProvider).getLoginLog(); - } on UnauthorizedFailure { - await ref.read(authProvider.notifier).signOut(); - rethrow; - } - }); - if (state.hasError && previous != null) state = AsyncData(previous); + try { + final log = await ref.read(settingsRepositoryProvider).getLoginLog(); + state = AsyncData(log); + } on UnauthorizedFailure { + await ref.read(authProvider.notifier).signOut(); + if (!throwOnError) return; + rethrow; + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; + } } } diff --git a/mobile/lib/state/plus_discount_notifier.dart b/mobile/lib/state/plus_discount_notifier.dart index 16d0dcf..6f3c6b3 100644 --- a/mobile/lib/state/plus_discount_notifier.dart +++ b/mobile/lib/state/plus_discount_notifier.dart @@ -11,12 +11,19 @@ class PlusDiscountNotifier extends AsyncNotifier { return ref.watch(settingsRepositoryProvider).getPlusDiscount(); } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; - state = await AsyncValue.guard( - () => ref.read(settingsRepositoryProvider).getPlusDiscount(), - ); - if (state.hasError && previous != null) state = AsyncData(previous); + try { + state = AsyncData( + await ref.read(settingsRepositoryProvider).getPlusDiscount(), + ); + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; + } } } diff --git a/mobile/lib/state/plus_info_notifier.dart b/mobile/lib/state/plus_info_notifier.dart index b716a6e..7790ef3 100644 --- a/mobile/lib/state/plus_info_notifier.dart +++ b/mobile/lib/state/plus_info_notifier.dart @@ -17,20 +17,23 @@ class PlusInfoNotifier extends AsyncNotifier { } } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) state = const AsyncLoading(); - state = await AsyncValue.guard( - () async { - try { - return await ref.read(settingsRepositoryProvider).getPlusInfo(); - } on UnauthorizedFailure { - await ref.read(authProvider.notifier).signOut(); - rethrow; - } - }, - ); - if (state.hasError && previous != null) state = AsyncData(previous); + try { + final plusInfo = await ref.read(settingsRepositoryProvider).getPlusInfo(); + state = AsyncData(plusInfo); + } on UnauthorizedFailure { + await ref.read(authProvider.notifier).signOut(); + if (!throwOnError) return; + rethrow; + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; + } } } diff --git a/mobile/lib/state/proxy_list_notifier.dart b/mobile/lib/state/proxy_list_notifier.dart index f9cd556..b905776 100644 --- a/mobile/lib/state/proxy_list_notifier.dart +++ b/mobile/lib/state/proxy_list_notifier.dart @@ -12,13 +12,18 @@ class ProxyListNotifier extends AsyncNotifier> { return _fetch(ref.watch(apiClientProvider)); } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) state = const AsyncLoading(); - state = await AsyncValue.guard( - () => _fetch(ref.read(apiClientProvider)), - ); - if (state.hasError && previous != null) state = AsyncData(previous); + try { + state = AsyncData(await _fetch(ref.read(apiClientProvider))); + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; + } } Future> _fetch(ApiClient api) async { diff --git a/mobile/lib/state/script_group_list_notifier.dart b/mobile/lib/state/script_group_list_notifier.dart index f423d22..86623f4 100644 --- a/mobile/lib/state/script_group_list_notifier.dart +++ b/mobile/lib/state/script_group_list_notifier.dart @@ -20,21 +20,24 @@ class ScriptGroupListNotifier extends AsyncNotifier> { } } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) { state = const AsyncLoading(); } - state = await AsyncValue.guard(() async { - try { - return await ref.read(scriptRepositoryProvider).fetchGroups(); - } on UnauthorizedFailure { - await ref.read(authProvider.notifier).signOut(); - rethrow; - } - }); - if (state.hasError && previous != null) { - state = AsyncData(previous); + try { + final groups = await ref.read(scriptRepositoryProvider).fetchGroups(); + state = AsyncData(groups); + } on UnauthorizedFailure { + await ref.read(authProvider.notifier).signOut(); + if (!throwOnError) return; + rethrow; + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; } } } diff --git a/mobile/lib/state/script_list_notifier.dart b/mobile/lib/state/script_list_notifier.dart index 358a17a..186d911 100644 --- a/mobile/lib/state/script_list_notifier.dart +++ b/mobile/lib/state/script_list_notifier.dart @@ -21,21 +21,24 @@ class ScriptListNotifier extends AsyncNotifier> { } } - Future refresh() async { + Future refresh({bool throwOnError = false}) async { final previous = state.valueOrNull; if (previous == null) { state = const AsyncLoading(); } - state = await AsyncValue.guard(() async { - try { - return await ref.read(scriptRepositoryProvider).fetchScripts(); - } on UnauthorizedFailure { - await ref.read(authProvider.notifier).signOut(); - rethrow; - } - }); - if (state.hasError && previous != null) { - state = AsyncData(previous); + try { + final scripts = await ref.read(scriptRepositoryProvider).fetchScripts(); + state = AsyncData(scripts); + } on UnauthorizedFailure { + await ref.read(authProvider.notifier).signOut(); + if (!throwOnError) return; + rethrow; + } catch (error, stackTrace) { + state = previous == null + ? AsyncError(error, stackTrace) + : AsyncData(previous); + if (!throwOnError) return; + rethrow; } } } diff --git a/mobile/lib/state/server_data_refresh.dart b/mobile/lib/state/server_data_refresh.dart index 573cfd3..abd2170 100644 --- a/mobile/lib/state/server_data_refresh.dart +++ b/mobile/lib/state/server_data_refresh.dart @@ -7,9 +7,9 @@ import 'proxy_list_notifier.dart'; Future refreshServerSharedData(WidgetRef ref) async { await Future.wait([ - ref.read(groupListProvider.notifier).refresh(), - ref.read(hostListProvider.notifier).refresh(), - ref.read(credentialListProvider.notifier).refresh(), - ref.read(proxyListProvider.notifier).refresh(), + ref.read(groupListProvider.notifier).refresh(throwOnError: true), + ref.read(hostListProvider.notifier).refresh(throwOnError: true), + ref.read(credentialListProvider.notifier).refresh(throwOnError: true), + ref.read(proxyListProvider.notifier).refresh(throwOnError: true), ]); }