diff --git a/lib/application/app_dependencies.dart b/lib/application/app_dependencies.dart index 7498313..7e5bb36 100644 --- a/lib/application/app_dependencies.dart +++ b/lib/application/app_dependencies.dart @@ -29,6 +29,7 @@ import 'package:subctrl/application/subscriptions/add_subscription_use_case.dart import 'package:subctrl/application/subscriptions/delete_subscription_use_case.dart'; import 'package:subctrl/application/subscriptions/update_subscription_use_case.dart'; import 'package:subctrl/application/subscriptions/watch_subscriptions_use_case.dart'; +import 'package:subctrl/application/support/fetch_donation_wallets_use_case.dart'; import 'package:subctrl/application/tags/create_tag_use_case.dart'; import 'package:subctrl/application/tags/delete_tag_use_case.dart'; import 'package:subctrl/application/tags/update_tag_use_case.dart'; @@ -48,6 +49,9 @@ import 'package:subctrl/infrastructure/repositories/drift_currency_repository.da import 'package:subctrl/infrastructure/repositories/drift_settings_repository.dart'; import 'package:subctrl/infrastructure/repositories/drift_subscription_repository.dart'; import 'package:subctrl/infrastructure/repositories/drift_tag_repository.dart'; +import 'package:subctrl/infrastructure/support/donation_wallet_fallback_data.dart'; +import 'package:subctrl/infrastructure/support/remote_donation_wallets_provider.dart'; +import 'package:subctrl/infrastructure/support/resilient_donation_wallets_provider.dart'; class AppDependencies { AppDependencies._({ @@ -86,8 +90,11 @@ class AppDependencies { required this.scheduleNotificationsUseCase, required this.requestNotificationPermissionUseCase, required this.openNotificationSettingsUseCase, + required this.fetchDonationWalletsUseCase, required YahooFinanceCurrencyClient yahooFinanceCurrencyClient, - }) : _yahooFinanceCurrencyClient = yahooFinanceCurrencyClient; + required RemoteDonationWalletsProvider remoteDonationWalletsProvider, + }) : _yahooFinanceCurrencyClient = yahooFinanceCurrencyClient, + _remoteDonationWalletsProvider = remoteDonationWalletsProvider; factory AppDependencies() { final database = AppDatabase(); @@ -110,6 +117,13 @@ class AppDependencies { yahooFinanceCurrencyClient: yahooFinanceClient, currencyRepository: currencyRepository, ); + final remoteDonationWalletsProvider = RemoteDonationWalletsProvider( + endpoint: Uri.parse('https://gonfff.com/subctrl/assets/wallets.json'), + ); + final donationWalletsProvider = ResilientDonationWalletsProvider( + primary: remoteDonationWalletsProvider, + fallbackWallets: donationWalletFallbackData, + ); final localNotificationsService = LocalNotificationsService(); final notificationPermissionService = NotificationPermissionService(); @@ -177,19 +191,22 @@ class AppDependencies { getPendingNotificationsUseCase: GetPendingNotificationsUseCase( localNotificationsService, ), - cancelNotificationsUseCase: - CancelNotificationsUseCase(localNotificationsService), + cancelNotificationsUseCase: CancelNotificationsUseCase( + localNotificationsService, + ), scheduleNotificationsUseCase: ScheduleNotificationsUseCase( localNotificationsService, ), requestNotificationPermissionUseCase: - RequestNotificationPermissionUseCase( - notificationPermissionService, - ), + RequestNotificationPermissionUseCase(notificationPermissionService), openNotificationSettingsUseCase: OpenNotificationSettingsUseCase( notificationPermissionService, ), + fetchDonationWalletsUseCase: FetchDonationWalletsUseCase( + donationWalletsProvider, + ), yahooFinanceCurrencyClient: yahooFinanceClient, + remoteDonationWalletsProvider: remoteDonationWalletsProvider, ); } @@ -235,10 +252,13 @@ class AppDependencies { final RequestNotificationPermissionUseCase requestNotificationPermissionUseCase; final OpenNotificationSettingsUseCase openNotificationSettingsUseCase; + final FetchDonationWalletsUseCase fetchDonationWalletsUseCase; final YahooFinanceCurrencyClient _yahooFinanceCurrencyClient; + final RemoteDonationWalletsProvider _remoteDonationWalletsProvider; void dispose() { _yahooFinanceCurrencyClient.close(); + _remoteDonationWalletsProvider.close(); } } diff --git a/lib/application/support/fetch_donation_wallets_use_case.dart b/lib/application/support/fetch_donation_wallets_use_case.dart new file mode 100644 index 0000000..be74aa7 --- /dev/null +++ b/lib/application/support/fetch_donation_wallets_use_case.dart @@ -0,0 +1,12 @@ +import 'package:subctrl/domain/entities/donation_wallet.dart'; +import 'package:subctrl/domain/services/donation_wallets_provider.dart'; + +class FetchDonationWalletsUseCase { + FetchDonationWalletsUseCase(this._provider); + + final DonationWalletsProvider _provider; + + Future> call() { + return _provider.fetchWallets(); + } +} diff --git a/lib/domain/entities/donation_wallet.dart b/lib/domain/entities/donation_wallet.dart new file mode 100644 index 0000000..c7214b4 --- /dev/null +++ b/lib/domain/entities/donation_wallet.dart @@ -0,0 +1,15 @@ +class DonationWallet { + const DonationWallet({ + required this.label, + required this.address, + this.currency, + this.name, + this.network, + }); + + final String label; + final String address; + final String? currency; + final String? name; + final String? network; +} diff --git a/lib/domain/services/donation_wallets_provider.dart b/lib/domain/services/donation_wallets_provider.dart new file mode 100644 index 0000000..627f013 --- /dev/null +++ b/lib/domain/services/donation_wallets_provider.dart @@ -0,0 +1,14 @@ +import 'package:subctrl/domain/entities/donation_wallet.dart'; + +abstract class DonationWalletsProvider { + Future> fetchWallets(); +} + +class DonationWalletsFetchException implements Exception { + DonationWalletsFetchException(this.message); + + final String message; + + @override + String toString() => 'DonationWalletsFetchException: $message'; +} diff --git a/lib/infrastructure/support/donation_wallet_fallback_data.dart b/lib/infrastructure/support/donation_wallet_fallback_data.dart new file mode 100644 index 0000000..2afb487 --- /dev/null +++ b/lib/infrastructure/support/donation_wallet_fallback_data.dart @@ -0,0 +1,39 @@ +import 'package:subctrl/domain/entities/donation_wallet.dart'; + +const List donationWalletFallbackData = [ + DonationWallet( + label: 'BTC', + address: 'bc1qjtjzlxel5mn3pvtps2u2wnfease44783r5nmhl', + currency: 'BTC', + name: 'Bitcoin', + network: 'Bitcoin', + ), + DonationWallet( + label: 'USDT (TRON)', + address: 'TRsN3XgSXdeQz3yoGusW3f94KHsBNE62yR', + currency: 'USDT', + name: 'Tether', + network: 'TRON (TRC20)', + ), + DonationWallet( + label: 'ETH', + address: '0xDf3275d97DF7Ba76d12ec0F82378C1e0628A5F6F', + currency: 'ETH', + name: 'Ethereum', + network: 'Ethereum', + ), + DonationWallet( + label: 'TON', + address: 'UQCYgQSiRx5pk5E0ALzhz6WsFjuK3SyPiAe7vYG5uhidsyqj', + currency: 'TON', + name: 'Toncoin', + network: 'TON', + ), + DonationWallet( + label: 'SOL', + address: '2KRt8ASpGasvMSaWZfgFfrFgb1LaUHzudHfGiEcF9vVK', + currency: 'SOL', + name: 'Solana', + network: 'Solana', + ), +]; diff --git a/lib/infrastructure/support/remote_donation_wallets_provider.dart b/lib/infrastructure/support/remote_donation_wallets_provider.dart new file mode 100644 index 0000000..b6bbedb --- /dev/null +++ b/lib/infrastructure/support/remote_donation_wallets_provider.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; +import 'dart:developer' as developer; + +import 'package:http/http.dart' as http; +import 'package:subctrl/domain/entities/donation_wallet.dart'; +import 'package:subctrl/domain/services/donation_wallets_provider.dart'; + +class RemoteDonationWalletsProvider implements DonationWalletsProvider { + RemoteDonationWalletsProvider({ + required Uri endpoint, + http.Client? httpClient, + }) : _endpoint = endpoint, + _httpClient = httpClient ?? http.Client(); + + final Uri _endpoint; + final http.Client _httpClient; + + static const _logName = 'RemoteDonationWalletsProvider'; + + @override + Future> fetchWallets() async { + try { + final response = await _httpClient.get(_endpoint); + if (response.statusCode != 200) { + throw DonationWalletsFetchException( + 'Wallet request failed with status ${response.statusCode}', + ); + } + return _parseResponse(response.body); + } catch (error, stackTrace) { + developer.log( + 'Failed to fetch donation wallets', + name: _logName, + error: error, + stackTrace: stackTrace, + ); + if (error is DonationWalletsFetchException) { + rethrow; + } + throw DonationWalletsFetchException('Unable to load donation wallets.'); + } + } + + List _parseResponse(String body) { + final decoded = json.decode(body); + if (decoded is! Map) { + throw DonationWalletsFetchException('Malformed wallets payload.'); + } + final wallets = decoded['wallets']; + if (wallets is! List) { + return const []; + } + final parsed = []; + for (final entry in wallets) { + if (entry is! Map) continue; + final label = entry['label']; + final address = entry['address']; + if (label is! String || address is! String) { + continue; + } + parsed.add( + DonationWallet( + label: label.trim(), + address: address.trim(), + currency: (entry['currency'] as String?)?.trim(), + name: (entry['name'] as String?)?.trim(), + network: (entry['network'] as String?)?.trim(), + ), + ); + } + return parsed; + } + + void close() => _httpClient.close(); +} diff --git a/lib/infrastructure/support/resilient_donation_wallets_provider.dart b/lib/infrastructure/support/resilient_donation_wallets_provider.dart new file mode 100644 index 0000000..db5229f --- /dev/null +++ b/lib/infrastructure/support/resilient_donation_wallets_provider.dart @@ -0,0 +1,39 @@ +import 'dart:developer' as developer; + +import 'package:subctrl/domain/entities/donation_wallet.dart'; +import 'package:subctrl/domain/services/donation_wallets_provider.dart'; + +class ResilientDonationWalletsProvider implements DonationWalletsProvider { + ResilientDonationWalletsProvider({ + required DonationWalletsProvider primary, + required List fallbackWallets, + }) : _primary = primary, + _fallbackWallets = List.unmodifiable(fallbackWallets); + + final DonationWalletsProvider _primary; + final List _fallbackWallets; + + static const _logName = 'ResilientDonationWalletsProvider'; + + @override + Future> fetchWallets() async { + try { + final wallets = await _primary.fetchWallets(); + if (wallets.isNotEmpty) { + return wallets; + } + developer.log( + 'Primary provider returned empty list, using fallback.', + name: _logName, + ); + } catch (error, stackTrace) { + developer.log( + 'Primary provider failed, returning fallback wallets.', + name: _logName, + error: error, + stackTrace: stackTrace, + ); + } + return _fallbackWallets; + } +} diff --git a/lib/presentation/l10n/app_localizations.dart b/lib/presentation/l10n/app_localizations.dart index 7674206..85dc544 100644 --- a/lib/presentation/l10n/app_localizations.dart +++ b/lib/presentation/l10n/app_localizations.dart @@ -81,6 +81,11 @@ class AppLocalizations { 'settingsAboutSupport': 'Support the project', 'settingsAboutVersion': 'App version', 'settingsCopyAction': 'Copy', + 'settingsSupportLoading': 'Loading wallets...', + 'settingsSupportError': + 'Failed to load wallets. Check your connection and try again.', + 'settingsSupportRetry': 'Retry', + 'settingsSupportEmpty': 'Wallet list is empty right now.', 'settingsCopySuccess': 'Copied to clipboard', 'settingsVersionUnknown': 'Unknown', 'settingsCurrenciesEnabledLabel': '{count} enabled', @@ -228,6 +233,11 @@ class AppLocalizations { 'settingsAboutSupport': 'Поддержать проект', 'settingsAboutVersion': 'Версия приложения', 'settingsCopyAction': 'Скопировать', + 'settingsSupportLoading': 'Загружаем кошельки...', + 'settingsSupportError': + 'Не удалось загрузить кошельки. Проверьте соединение и попробуйте ещё раз.', + 'settingsSupportRetry': 'Повторить', + 'settingsSupportEmpty': 'Список кошельков пока пуст.', 'settingsCopySuccess': 'Скопировано', 'settingsVersionUnknown': 'Неизвестно', 'settingsCurrenciesEnabledLabel': '{count} активны', @@ -422,6 +432,10 @@ class AppLocalizations { String get settingsAboutSupport => _strings['settingsAboutSupport']!; String get settingsAboutVersion => _strings['settingsAboutVersion']!; String get settingsCopyAction => _strings['settingsCopyAction']!; + String get settingsSupportLoading => _strings['settingsSupportLoading']!; + String get settingsSupportError => _strings['settingsSupportError']!; + String get settingsSupportRetry => _strings['settingsSupportRetry']!; + String get settingsSupportEmpty => _strings['settingsSupportEmpty']!; String get settingsCopySuccess => _strings['settingsCopySuccess']!; String get settingsVersionUnknown => _strings['settingsVersionUnknown']!; String get settingsCurrenciesTitle => _strings['settingsCurrenciesTitle']!; diff --git a/lib/presentation/screens/settings_screen.dart b/lib/presentation/screens/settings_screen.dart index 8d18984..0b272d0 100644 --- a/lib/presentation/screens/settings_screen.dart +++ b/lib/presentation/screens/settings_screen.dart @@ -306,7 +306,11 @@ class _SettingsScreenState extends State { Future _openSupport() async { await Navigator.of(context).push( CupertinoPageRoute( - builder: (context) => SupportScreen(onClose: widget.onRequestClose), + builder: (context) => SupportScreen( + onClose: widget.onRequestClose, + fetchDonationWalletsUseCase: + widget.dependencies.fetchDonationWalletsUseCase, + ), ), ); } diff --git a/lib/presentation/screens/support_screen.dart b/lib/presentation/screens/support_screen.dart index 2f45c65..69bf318 100644 --- a/lib/presentation/screens/support_screen.dart +++ b/lib/presentation/screens/support_screen.dart @@ -1,40 +1,32 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; +import 'package:subctrl/application/support/fetch_donation_wallets_use_case.dart'; +import 'package:subctrl/domain/entities/donation_wallet.dart'; import 'package:subctrl/presentation/l10n/app_localizations.dart'; import 'package:subctrl/presentation/theme/app_theme.dart'; class SupportScreen extends StatefulWidget { - const SupportScreen({super.key, required this.onClose}); + const SupportScreen({ + super.key, + required this.onClose, + required this.fetchDonationWalletsUseCase, + }); final VoidCallback onClose; + final FetchDonationWalletsUseCase fetchDonationWalletsUseCase; @override State createState() => _SupportScreenState(); } class _SupportScreenState extends State { - static const List<_DonationEntry> _donations = [ - _DonationEntry( - label: 'BTC', - value: 'bc1qjtjzlxel5mn3pvtps2u2wnfease44783r5nmhl', - ), - _DonationEntry( - label: 'USDT (TRON)', - value: 'TRsN3XgSXdeQz3yoGusW3f94KHsBNE62yR', - ), - _DonationEntry( - label: 'ETH', - value: '0xDf3275d97DF7Ba76d12ec0F82378C1e0628A5F6F', - ), - _DonationEntry( - label: 'TON', - value: 'UQCYgQSiRx5pk5E0ALzhz6WsFjuK3SyPiAe7vYG5uhidsyqj', - ), - _DonationEntry( - label: 'SOL', - value: '2KRt8ASpGasvMSaWZfgFfrFgb1LaUHzudHfGiEcF9vVK', - ), - ]; + late Future> _walletsFuture; + + @override + void initState() { + super.initState(); + _walletsFuture = widget.fetchDonationWalletsUseCase(); + } Future _copyValue(String value) async { await Clipboard.setData(ClipboardData(text: value)); @@ -49,6 +41,12 @@ class _SupportScreenState extends State { }); } + void _reloadWallets() { + setState(() { + _walletsFuture = widget.fetchDonationWalletsUseCase(); + }); + } + @override Widget build(BuildContext context) { final localizations = AppLocalizations.of(context); @@ -62,19 +60,52 @@ class _SupportScreenState extends State { middle: Text(localizations.settingsAboutSupport), ), child: SafeArea( - child: ListView( - children: [ - CupertinoFormSection.insetGrouped( - header: Text(localizations.settingsAboutSupport), - children: [ - _DonationTile( - entries: _donations, - copyLabel: localizations.settingsCopyAction, - onCopy: _copyValue, - ), - ], - ), - ], + child: FutureBuilder>( + future: _walletsFuture, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + case ConnectionState.active: + return _SupportMessageView( + message: localizations.settingsSupportLoading, + child: const CupertinoActivityIndicator(radius: 12), + ); + case ConnectionState.none: + return _SupportErrorView( + message: localizations.settingsSupportError, + retryLabel: localizations.settingsSupportRetry, + onRetry: _reloadWallets, + ); + case ConnectionState.done: + if (snapshot.hasError) { + return _SupportErrorView( + message: localizations.settingsSupportError, + retryLabel: localizations.settingsSupportRetry, + onRetry: _reloadWallets, + ); + } + final wallets = snapshot.data ?? const []; + if (wallets.isEmpty) { + return _SupportMessageView( + message: localizations.settingsSupportEmpty, + ); + } + return ListView( + children: [ + CupertinoFormSection.insetGrouped( + header: Text(localizations.settingsAboutSupport), + children: [ + _DonationTile( + entries: wallets, + copyLabel: localizations.settingsCopyAction, + onCopy: _copyValue, + ), + ], + ), + ], + ); + } + }, ), ), ); @@ -88,7 +119,7 @@ class _DonationTile extends StatelessWidget { required this.onCopy, }); - final List<_DonationEntry> entries; + final List entries; final String copyLabel; final ValueChanged onCopy; @@ -122,7 +153,7 @@ class _DonationTile extends StatelessWidget { ), const SizedBox(height: 4), Text( - entries[i].value, + entries[i].address, style: textStyle.copyWith( fontSize: 13, color: secondary, @@ -136,7 +167,7 @@ class _DonationTile extends StatelessWidget { horizontal: 12, vertical: 6, ), - onPressed: () => onCopy(entries[i].value), + onPressed: () => onCopy(entries[i].address), child: Text(copyLabel), ), ], @@ -148,9 +179,54 @@ class _DonationTile extends StatelessWidget { } } -class _DonationEntry { - const _DonationEntry({required this.label, required this.value}); +class _SupportMessageView extends StatelessWidget { + const _SupportMessageView({required this.message, this.child}); - final String label; - final String value; + final String message; + final Widget? child; + + @override + Widget build(BuildContext context) { + final textStyle = CupertinoTheme.of(context).textTheme.textStyle; + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (child != null) ...[child!, const SizedBox(height: 12)], + Text( + message, + textAlign: TextAlign.center, + style: textStyle.copyWith(fontSize: 15), + ), + ], + ), + ), + ); + } +} + +class _SupportErrorView extends StatelessWidget { + const _SupportErrorView({ + required this.message, + required this.retryLabel, + required this.onRetry, + }); + + final String message; + final String retryLabel; + final VoidCallback onRetry; + + @override + Widget build(BuildContext context) { + return _SupportMessageView( + message: message, + child: CupertinoButton( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + onPressed: onRetry, + child: Text(retryLabel), + ), + ); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 9be27dd..bd31616 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.1+6 +version: 1.0.2+7 environment: sdk: ^3.10.3 diff --git a/test/infrastructure/support/remote_donation_wallets_provider_test.dart b/test/infrastructure/support/remote_donation_wallets_provider_test.dart new file mode 100644 index 0000000..d6687d1 --- /dev/null +++ b/test/infrastructure/support/remote_donation_wallets_provider_test.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:mocktail/mocktail.dart'; +import 'package:subctrl/domain/services/donation_wallets_provider.dart'; +import 'package:subctrl/infrastructure/support/remote_donation_wallets_provider.dart'; + +class _MockHttpClient extends Mock implements http.Client {} + +void main() { + late _MockHttpClient httpClient; + late RemoteDonationWalletsProvider provider; + final endpoint = Uri.parse('https://example.com/wallets.json'); + + setUp(() { + httpClient = _MockHttpClient(); + provider = RemoteDonationWalletsProvider( + endpoint: endpoint, + httpClient: httpClient, + ); + }); + + tearDown(() { + provider.close(); + }); + + test('fetchWallets returns parsed wallets from payload', () async { + when(() => httpClient.get(endpoint)).thenAnswer( + (_) async => http.Response( + jsonEncode({ + 'wallets': [ + { + 'label': 'BTC', + 'address': 'btc-address', + 'currency': 'BTC', + 'name': 'Bitcoin', + 'network': 'Bitcoin', + }, + {'label': 10, 'address': 'skip-invalid'}, + ], + }), + 200, + ), + ); + + final wallets = await provider.fetchWallets(); + + expect(wallets, hasLength(1)); + expect(wallets.first.label, 'BTC'); + expect(wallets.first.address, 'btc-address'); + }); + + test( + 'fetchWallets throws DonationWalletsFetchException on error status', + () async { + when( + () => httpClient.get(endpoint), + ).thenAnswer((_) async => http.Response('error', 500)); + + await expectLater( + provider.fetchWallets, + throwsA(isA()), + ); + }, + ); +} diff --git a/test/infrastructure/support/resilient_donation_wallets_provider_test.dart b/test/infrastructure/support/resilient_donation_wallets_provider_test.dart new file mode 100644 index 0000000..6be0fd5 --- /dev/null +++ b/test/infrastructure/support/resilient_donation_wallets_provider_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:subctrl/domain/entities/donation_wallet.dart'; +import 'package:subctrl/domain/services/donation_wallets_provider.dart'; +import 'package:subctrl/infrastructure/support/resilient_donation_wallets_provider.dart'; + +class _MockDonationWalletsProvider extends Mock + implements DonationWalletsProvider {} + +void main() { + late _MockDonationWalletsProvider primary; + late ResilientDonationWalletsProvider provider; + const fallbackWallets = [DonationWallet(label: 'BTC', address: 'btc')]; + + setUp(() { + primary = _MockDonationWalletsProvider(); + provider = ResilientDonationWalletsProvider( + primary: primary, + fallbackWallets: fallbackWallets, + ); + }); + + test('returns primary wallets when available', () async { + when(() => primary.fetchWallets()).thenAnswer( + (_) async => const [DonationWallet(label: 'SOL', address: 'sol')], + ); + + final wallets = await provider.fetchWallets(); + + expect(wallets.single.label, 'SOL'); + }); + + test('returns fallback when primary returns empty list', () async { + when(() => primary.fetchWallets()).thenAnswer((_) async => const []); + + final wallets = await provider.fetchWallets(); + + expect(wallets.single.label, 'BTC'); + }); + + test('returns fallback when primary throws', () async { + when(() => primary.fetchWallets()).thenThrow(Exception('network')); + + final wallets = await provider.fetchWallets(); + + expect(wallets.single.address, 'btc'); + }); +} diff --git a/test/presentation/l10n/app_localizations_test.dart b/test/presentation/l10n/app_localizations_test.dart index 6be2d59..71fe2a3 100644 --- a/test/presentation/l10n/app_localizations_test.dart +++ b/test/presentation/l10n/app_localizations_test.dart @@ -109,6 +109,10 @@ void main() { localizations.settingsAboutSupport, localizations.settingsAboutVersion, localizations.settingsCopyAction, + localizations.settingsSupportLoading, + localizations.settingsSupportError, + localizations.settingsSupportRetry, + localizations.settingsSupportEmpty, localizations.settingsCopySuccess, localizations.settingsVersionUnknown, localizations.settingsCurrenciesTitle,