diff --git a/lib/core/trade_monitor.dart b/lib/core/trade_monitor.dart new file mode 100644 index 000000000..3b696b88a --- /dev/null +++ b/lib/core/trade_monitor.dart @@ -0,0 +1,247 @@ +import 'dart:async'; +import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/store/dashboard/trades_store.dart'; +import 'package:cake_wallet/entities/exchange_api_mode.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; +import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class TradeMonitor { + static const int _tradeCheckIntervalMinutes = 5; + static const int _maxTradeAgeHours = 24; + + TradeMonitor({ + required this.tradesStore, + required this.trades, + required this.appStore, + required this.preferences, + }); + + final TradesStore tradesStore; + final Box trades; + final AppStore appStore; + final Map _tradeTimers = {}; + final SharedPreferences preferences; + + ExchangeProvider? _getProviderByDescription(ExchangeProviderDescription description) { + switch (description) { + case ExchangeProviderDescription.changeNow: + return ChangeNowExchangeProvider(settingsStore: appStore.settingsStore); + case ExchangeProviderDescription.sideShift: + return SideShiftExchangeProvider(); + case ExchangeProviderDescription.simpleSwap: + return SimpleSwapExchangeProvider(); + case ExchangeProviderDescription.trocador: + return TrocadorExchangeProvider(); + case ExchangeProviderDescription.exolix: + return ExolixExchangeProvider(); + case ExchangeProviderDescription.thorChain: + return ThorChainExchangeProvider(tradesStore: trades); + case ExchangeProviderDescription.swapTrade: + return SwapTradeExchangeProvider(); + case ExchangeProviderDescription.letsExchange: + return LetsExchangeExchangeProvider(); + case ExchangeProviderDescription.stealthEx: + return StealthExExchangeProvider(); + case ExchangeProviderDescription.chainflip: + return ChainflipExchangeProvider(tradesStore: trades); + case ExchangeProviderDescription.xoSwap: + return XOSwapExchangeProvider(); + } + return null; + } + + void monitorActiveTrades(String walletId) { + // Checks if the trade monitoring is permitted + // i.e the user has not disabled the exchange api mode or the status updates + final isTradeMonitoringPermitted = _isTradeMonitoringPermitted(); + if (!isTradeMonitoringPermitted) { + return; + } + + final trades = tradesStore.trades; + final tradesToCancel = []; + + for (final item in trades) { + final trade = item.trade; + + final provider = _getProviderByDescription(trade.provider); + + // Multiple checks to see if to skip the trade, if yes, we cancel the timer if it exists + if (_shouldSkipTrade(trade, walletId, provider)) { + tradesToCancel.add(trade.id); + continue; + } + + if (_tradeTimers.containsKey(trade.id)) { + printV('Trade ${trade.id} is already being monitored'); + continue; + } else { + _startTradeMonitoring(trade, provider!); + } + } + + // After going through the list of available trades, we cancel the timers in the tradesToCancel list + _cancelMultipleTradeTimers(tradesToCancel); + } + + bool _isTradeMonitoringPermitted() { + final disableAutomaticExchangeStatusUpdates = + appStore.settingsStore.disableAutomaticExchangeStatusUpdates; + if (disableAutomaticExchangeStatusUpdates) { + printV('Automatic exchange status updates are disabled'); + return false; + } + + final exchangeApiMode = appStore.settingsStore.exchangeStatus; + if (exchangeApiMode == ExchangeApiMode.disabled) { + printV('Exchange API mode is disabled'); + return false; + } + + return true; + } + + bool _shouldSkipTrade(Trade trade, String walletId, ExchangeProvider? provider) { + if (trade.walletId != walletId) { + printV('Skipping trade ${trade.id} because it\'s not for this wallet'); + return true; + } + + final createdAt = trade.createdAt; + if (createdAt == null) { + printV('Skipping trade ${trade.id} because it has no createdAt'); + return true; + } + + if (DateTime.now().difference(createdAt).inHours > _maxTradeAgeHours) { + printV('Skipping trade ${trade.id} because it\'s older than ${_maxTradeAgeHours} hours'); + return true; + } + + if (_isFinalState(trade.state)) { + printV('Skipping trade ${trade.id} because it\'s in a final state'); + return true; + } + + if (provider == null) { + printV('Skipping trade ${trade.id} because the provider is not supported'); + return true; + } + + if (appStore.settingsStore.exchangeStatus == ExchangeApiMode.torOnly && + !provider.supportsOnionAddress) { + printV('Skipping ${provider.description}, no TOR support'); + return true; + } + + return false; + } + + void _startTradeMonitoring(Trade trade, ExchangeProvider provider) { + final timer = Timer.periodic( + Duration(minutes: _tradeCheckIntervalMinutes), + (_) => _checkTradeStatus(trade, provider), + ); + + _checkTradeStatus(trade, provider); + + _tradeTimers[trade.id] = timer; + } + + Future _checkTradeStatus(Trade trade, ExchangeProvider provider) async { + final lastUpdatedAtFromPrefs = preferences.getString('trade_${trade.id}_updated_at'); + + if (lastUpdatedAtFromPrefs != null) { + final lastUpdatedAtDateTime = DateTime.parse(lastUpdatedAtFromPrefs); + final timeSinceLastUpdate = DateTime.now().difference(lastUpdatedAtDateTime).inMinutes; + + if (timeSinceLastUpdate < _tradeCheckIntervalMinutes) { + printV( + 'Skipping trade ${trade.id} status update check because it was updated less than ${_tradeCheckIntervalMinutes} minutes ago ($timeSinceLastUpdate minutes ago)', + ); + return; + } + } + + try { + final updated = await provider.findTradeById(id: trade.id); + trade + ..stateRaw = updated.state.raw + ..receiveAmount = updated.receiveAmount ?? trade.receiveAmount + ..outputTransaction = updated.outputTransaction ?? trade.outputTransaction; + printV('Trade ${trade.id} updated: ${trade.state}'); + await trade.save(); + + await preferences.setString('trade_${trade.id}_updated_at', DateTime.now().toIso8601String()); + printV('Trade ${trade.id} updated at: ${DateTime.now().toIso8601String()}'); + + // If the updated trade is in a final state, we cancel the timer + if (_isFinalState(updated.state)) { + printV('Trade ${trade.id} is in final state'); + _cancelSingleTradeTimer(trade.id); + } + } catch (e) { + printV('Error fetching status for ${trade.id}: $e'); + } + } + + bool _isFinalState(TradeState state) { + return { + TradeState.completed.raw, + TradeState.success.raw, + TradeState.confirmed.raw, + TradeState.settled.raw, + TradeState.finished.raw, + TradeState.expired.raw, + TradeState.failed.raw, + TradeState.notFound.raw, + }.contains(state.raw); + } + + void _cancelSingleTradeTimer(String tradeId) { + if (_tradeTimers.containsKey(tradeId)) { + _tradeTimers[tradeId]?.cancel(); + _tradeTimers.remove(tradeId); + printV('Trade timer for ${tradeId} cancelled'); + } + } + + void _cancelMultipleTradeTimers(List tradeIds) { + for (final tradeId in tradeIds) { + _cancelSingleTradeTimer(tradeId); + } + } + + /// This is called when the app is brought back to foreground. + void resumeTradeMonitoring() { + if (appStore.wallet != null) { + monitorActiveTrades(appStore.wallet!.id); + } + } + + /// There's no need to run the trade checks when the app is in background. + /// We only want to update the trade status when the app is in foreground. + /// This helps to reduce the battery usage, network usage and enhance overall privacy. + /// + /// This is called when the app is sent to background or when the app is closed. + void stopTradeMonitoring() { + printV('Stopping trade monitoring'); + _cancelMultipleTradeTimers(_tradeTimers.keys.toList()); + } +} diff --git a/lib/di.dart b/lib/di.dart index 140a26698..1d925150f 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -275,6 +275,7 @@ import 'src/screens/buy/buy_sell_page.dart'; import 'cake_pay/cake_pay_payment_credantials.dart'; import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart'; import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart'; +import 'package:cake_wallet/core/trade_monitor.dart'; final getIt = GetIt.instance; @@ -507,19 +508,42 @@ Future setup({ settingsStore: getIt.get(), fiatConvertationStore: getIt.get())); - getIt.registerFactory(() => DashboardViewModel( - balanceViewModel: getIt.get(), - appStore: getIt.get(), + getIt.registerFactory( + () => ExchangeViewModel( + getIt.get(), + _tradesSource, + getIt.get(), + getIt.get(), + getIt.get().settingsStore, + getIt.get(), + getIt.get(), + getIt.get(), + ), + ); + + getIt.registerSingleton( + TradeMonitor( tradesStore: getIt.get(), - tradeFilterStore: getIt.get(), - transactionFilterStore: getIt.get(), - settingsStore: settingsStore, - yatStore: getIt.get(), - ordersStore: getIt.get(), - anonpayTransactionsStore: getIt.get(), - payjoinTransactionsStore: getIt.get(), - sharedPreferences: getIt.get(), - keyService: getIt.get())); + trades: _tradesSource, + appStore: getIt.get(), + preferences: getIt.get(), + ), + ); + + getIt.registerFactory(() => DashboardViewModel( + tradeMonitor: getIt.get(), + balanceViewModel: getIt.get(), + appStore: getIt.get(), + tradesStore: getIt.get(), + tradeFilterStore: getIt.get(), + transactionFilterStore: getIt.get(), + settingsStore: settingsStore, + yatStore: getIt.get(), + ordersStore: getIt.get(), + anonpayTransactionsStore: getIt.get(), + payjoinTransactionsStore: getIt.get(), + sharedPreferences: getIt.get(), + keyService: getIt.get())); getIt.registerFactory( () => AuthService( @@ -1051,19 +1075,6 @@ Future setup({ getIt.registerFactoryParam((title, uri) => WebViewPage(title, uri)); - getIt.registerFactory( - () => ExchangeViewModel( - getIt.get(), - _tradesSource, - getIt.get(), - getIt.get(), - getIt.get(), - getIt.get(), - getIt.get(), - getIt.get(), - ), - ); - getIt.registerFactory( () => FeesViewModel( getIt.get(), diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 238ad500c..9e384f462 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -24,6 +24,7 @@ class PreferencesKey { static const shouldSaveRecipientAddressKey = 'save_recipient_address'; static const isAppSecureKey = 'is_app_secure'; static const disableTradeOption = 'disable_buy'; + static const disableAutomaticExchangeStatusUpdates = 'disable_automatic_exchange_status_updates'; static const disableBulletinKey = 'disable_bulletin'; static const walletListOrder = 'wallet_list_order'; static const contactListOrder = 'contact_list_order'; diff --git a/lib/main.dart b/lib/main.dart index 2ea31bf03..6795b7ff9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -50,6 +50,7 @@ import 'package:cw_core/root_dir.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cw_core/window_size.dart'; import 'package:logging/logging.dart'; +import 'package:cake_wallet/core/trade_monitor.dart'; final navigatorKey = GlobalKey(); final rootKey = GlobalKey(); @@ -297,6 +298,7 @@ class AppState extends State with SingleTickerProviderStateMixin { final appStore = getIt.get(); final authService = getIt.get(); final linkViewModel = getIt.get(); + final tradeMonitor = getIt.get(); final statusBarColor = Colors.transparent; final authenticationStore = getIt.get(); final initialRoute = authenticationStore.state == AuthenticationState.uninitialized @@ -317,6 +319,7 @@ class AppState extends State with SingleTickerProviderStateMixin { navigatorKey: navigatorKey, authService: authService, linkViewModel: linkViewModel, + tradeMonitor: tradeMonitor, child: ThemeProvider( themeStore: appStore.themeStore, materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp( diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index 47e900959..7a3aa156a 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -166,17 +166,19 @@ class TransactionsPage extends StatelessWidget { return Observer( builder: (_) => TradeRow( - key: item.key, - onTap: () => Navigator.of(context) - .pushNamed(Routes.tradeDetails, arguments: trade), + key: item.key, + onTap: () => Navigator.of(context) + .pushNamed(Routes.tradeDetails, arguments: trade), + swapState: trade.state, provider: trade.provider, - from: trade.from, - to: trade.to, - createdAtFormattedDate: trade.createdAt != null - ? DateFormat('HH:mm').format(trade.createdAt!) - : null, - formattedAmount: item.tradeFormattedAmount, - formattedReceiveAmount: item.tradeFormattedReceiveAmount), + from: trade.from, + to: trade.to, + createdAtFormattedDate: trade.createdAt != null + ? DateFormat('HH:mm').format(trade.createdAt!) + : null, + formattedAmount: item.tradeFormattedAmount, + formattedReceiveAmount: item.tradeFormattedReceiveAmount + ), ); } if (item is OrderListItem) { diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index 9f04d587d..e50cb9718 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -1,7 +1,9 @@ +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/utils/image_utill.dart'; import 'package:flutter/material.dart'; -import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cw_core/crypto_currency.dart'; class TradeRow extends StatelessWidget { TradeRow({ @@ -12,6 +14,7 @@ class TradeRow extends StatelessWidget { this.onTap, this.formattedAmount, this.formattedReceiveAmount, + required this.swapState, super.key, }); @@ -22,6 +25,7 @@ class TradeRow extends StatelessWidget { final String? createdAtFormattedDate; final String? formattedAmount; final String? formattedReceiveAmount; + final TradeState swapState; @override Widget build(BuildContext context) { @@ -29,25 +33,39 @@ class TradeRow extends StatelessWidget { final receiveAmountCrypto = to.toString(); return InkWell( - onTap: onTap, - child: Container( - padding: EdgeInsets.fromLTRB(24, 8, 24, 8), - color: Colors.transparent, - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(50), - child: ImageUtil.getImageFromPath( - imagePath: provider.image, - height: 36, - width: 36, + onTap: onTap, + child: Container( + padding: EdgeInsets.fromLTRB(24, 8, 24, 8), + color: Colors.transparent, + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(50), + child: ImageUtil.getImageFromPath( + imagePath: provider.image, height: 36, width: 36),), + Positioned( + right: 0, + bottom: 2, + child: Container( + height: 8, + width: 8, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: _statusColor(context, swapState), + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ], ), - ), - SizedBox(width: 12), - Expanded( - child: Column( + SizedBox(width: 12), + Expanded( + child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( @@ -104,4 +122,21 @@ class TradeRow extends StatelessWidget { ), ); } + + Color _statusColor(BuildContext context, TradeState status) { + switch (status) { + case TradeState.complete: + case TradeState.completed: + case TradeState.finished: + case TradeState.success: + case TradeState.settled: + return PaletteDark.brightGreen; + case TradeState.failed: + case TradeState.expired: + case TradeState.notFound: + return Palette.darkRed; + default: + return const Color(0xffff6600); + } + } } diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index 6112673b1..19e8cab5f 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/totp_request_details.dart'; +import 'package:cake_wallet/core/trade_monitor.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/utils/print_verbose.dart'; @@ -27,6 +28,7 @@ class Root extends StatefulWidget { required this.navigatorKey, required this.authService, required this.linkViewModel, + required this.tradeMonitor, }) : super(key: key); final AuthenticationStore authenticationStore; @@ -35,6 +37,7 @@ class Root extends StatefulWidget { final AuthService authService; final Widget child; final LinkViewModel linkViewModel; + final TradeMonitor tradeMonitor; @override RootState createState() => RootState(); @@ -141,6 +144,8 @@ class RootState extends State with WidgetsBindingObserver { bitcoin!.stopPayjoinSessions(widget.appStore.wallet!); } + widget.tradeMonitor.stopTradeMonitoring(); + break; case AppLifecycleState.resumed: widget.authService.requireAuth().then((value) { @@ -154,6 +159,8 @@ class RootState extends State with WidgetsBindingObserver { widget.appStore.settingsStore.usePayjoin) { bitcoin!.resumePayjoinSessions(widget.appStore.wallet!); } + + widget.tradeMonitor.resumeTradeMonitoring(); break; default: break; diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index 2d1856eca..792891453 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -75,30 +75,41 @@ class PrivacyPage extends BasePage { ), if (DeviceInfo.instance.isMobile) SettingsSwitcherCell( - title: S.current.prevent_screenshots, - value: _privacySettingsViewModel.isAppSecure, - onValueChange: (BuildContext _, bool value) { - _privacySettingsViewModel.setIsAppSecure(value); - }), - SettingsSwitcherCell( - title: S.current.disable_buy, - value: _privacySettingsViewModel.disableTradeOption, + title: S.current.prevent_screenshots, + value: _privacySettingsViewModel.isAppSecure, onValueChange: (BuildContext _, bool value) { - _privacySettingsViewModel.setDisableTradeOption(value); - }), + _privacySettingsViewModel.setIsAppSecure(value); + }, + ), SettingsSwitcherCell( - title: S.current.disable_bulletin, - value: _privacySettingsViewModel.disableBulletin, - onValueChange: (BuildContext _, bool value) { - _privacySettingsViewModel.setDisableBulletin(value); - }), + title: S.current.disable_buy, + value: _privacySettingsViewModel.disableTradeOption, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setDisableTradeOption(value); + }, + ), + SettingsSwitcherCell( + title: S.current.disable_automatic_exchange_status_updates, + value: _privacySettingsViewModel.disableAutomaticExchangeStatusUpdates, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setDisableAutomaticExchangeStatusUpdates(value); + }, + ), + SettingsSwitcherCell( + title: S.current.disable_bulletin, + value: _privacySettingsViewModel.disableBulletin, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setDisableBulletin(value); + }, + ), if (_privacySettingsViewModel.canUseEtherscan) SettingsSwitcherCell( - title: S.current.etherscan_history, - value: _privacySettingsViewModel.useEtherscan, - onValueChange: (BuildContext _, bool value) { - _privacySettingsViewModel.setUseEtherscan(value); - }), + title: S.current.etherscan_history, + value: _privacySettingsViewModel.useEtherscan, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setUseEtherscan(value); + }, + ), if (_privacySettingsViewModel.canUsePolygonScan) SettingsSwitcherCell( title: S.current.polygonscan_history, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index fb02dcc21..c88590475 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -64,6 +64,7 @@ abstract class SettingsStoreBase with Store { required NanoSeedType initialNanoSeedType, required bool initialAppSecure, required bool initialDisableTrade, + required bool initialDisableAutomaticExchangeStatusUpdates, required FilterListOrderType initialWalletListOrder, required FilterListOrderType initialContactListOrder, required bool initialDisableBulletin, @@ -156,6 +157,7 @@ abstract class SettingsStoreBase with Store { numberOfFailedTokenTrials = initialFailedTokenTrial, isAppSecure = initialAppSecure, disableTradeOption = initialDisableTrade, + disableAutomaticExchangeStatusUpdates = initialDisableAutomaticExchangeStatusUpdates, disableBulletin = initialDisableBulletin, walletListOrder = initialWalletListOrder, contactListOrder = initialContactListOrder, @@ -307,6 +309,9 @@ abstract class SettingsStoreBase with Store { reaction((_) => disableTradeOption, (bool disableTradeOption) => sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption)); + reaction((_) => disableAutomaticExchangeStatusUpdates, + (bool disableAutomaticExchangeStatusUpdates) => sharedPreferences.setBool(PreferencesKey.disableAutomaticExchangeStatusUpdates, disableAutomaticExchangeStatusUpdates)); + reaction( (_) => disableBulletin, (bool disableBulletin) => @@ -675,6 +680,9 @@ abstract class SettingsStoreBase with Store { @observable bool disableTradeOption; + @observable + bool disableAutomaticExchangeStatusUpdates; + @observable FilterListOrderType contactListOrder; @@ -956,6 +964,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false; final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false; final disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? false; + final disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? false; final disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? false; final walletListOrder = FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0]; @@ -1273,6 +1282,7 @@ abstract class SettingsStoreBase with Store { initialNanoSeedType: nanoSeedType, initialAppSecure: isAppSecure, initialDisableTrade: disableTradeOption, + initialDisableAutomaticExchangeStatusUpdates: disableAutomaticExchangeStatusUpdates, initialDisableBulletin: disableBulletin, initialWalletListOrder: walletListOrder, initialWalletListAscending: walletListAscending, @@ -1436,6 +1446,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials; isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? disableTradeOption; + disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? disableAutomaticExchangeStatusUpdates; disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin; walletListOrder = diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 0202d0d13..30bd1c8b3 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -23,7 +23,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart'; import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; -import 'package:cake_wallet/themes/core/material_base_theme.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; @@ -56,6 +55,8 @@ import 'package:mobx/mobx.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/core/trade_monitor.dart'; + part 'dashboard_view_model.g.dart'; class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel; @@ -63,6 +64,7 @@ class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel; abstract class DashboardViewModelBase with Store { DashboardViewModelBase( {required this.balanceViewModel, + required this.tradeMonitor, required this.appStore, required this.tradesStore, required this.tradeFilterStore, @@ -270,6 +272,9 @@ abstract class DashboardViewModelBase with Store { _checkMweb(); showDecredInfoCard = wallet?.type == WalletType.decred && sharedPreferences.getBool(PreferencesKey.showDecredInfoCard) != false; + + tradeMonitor.stopTradeMonitoring(); + tradeMonitor.monitorActiveTrades(wallet!.id); }); _transactionDisposer?.reaction.dispose(); @@ -298,6 +303,10 @@ abstract class DashboardViewModelBase with Store { _checkMweb(); reaction((_) => settingsStore.mwebAlwaysScan, (bool value) => _checkMweb()); + + reaction((_) => tradesStore.trades, (_) => tradeMonitor.monitorActiveTrades(wallet.id)); + + tradeMonitor.monitorActiveTrades(wallet.id); } bool _isTransactionDisposerCallbackRunning = false; @@ -773,6 +782,8 @@ abstract class DashboardViewModelBase with Store { BalanceViewModel balanceViewModel; + TradeMonitor tradeMonitor; + AppStore appStore; SettingsStore settingsStore; diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index 05913befb..e78f61ec4 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -33,9 +33,8 @@ abstract class PrivacySettingsViewModelBase with Store { @action void setAutoGenerateSubaddresses(bool value) { _wallet.isEnabledAutoGenerateSubaddress = value; - _settingsStore.autoGenerateSubaddressStatus = value - ? AutoGenerateSubaddressStatus.enabled - : AutoGenerateSubaddressStatus.disabled; + _settingsStore.autoGenerateSubaddressStatus = + value ? AutoGenerateSubaddressStatus.enabled : AutoGenerateSubaddressStatus.disabled; } bool get isAutoGenerateSubaddressesVisible => [ @@ -61,6 +60,10 @@ abstract class PrivacySettingsViewModelBase with Store { @computed bool get disableTradeOption => _settingsStore.disableTradeOption; + @computed + bool get disableAutomaticExchangeStatusUpdates => + _settingsStore.disableAutomaticExchangeStatusUpdates; + @computed bool get disableBulletin => _settingsStore.disableBulletin; @@ -129,6 +132,10 @@ abstract class PrivacySettingsViewModelBase with Store { @action void setDisableTradeOption(bool value) => _settingsStore.disableTradeOption = value; + @action + void setDisableAutomaticExchangeStatusUpdates(bool value) => + _settingsStore.disableAutomaticExchangeStatusUpdates = value; + @action void setDisableBulletin(bool value) => _settingsStore.disableBulletin = value; @@ -146,7 +153,7 @@ abstract class PrivacySettingsViewModelBase with Store { @action void setLookupsWellKnown(bool value) => _settingsStore.lookupsWellKnown = value; - + @action void setLookupsYatService(bool value) => _settingsStore.lookupsYatService = value; @@ -175,8 +182,7 @@ abstract class PrivacySettingsViewModelBase with Store { } @action - void setUseMempoolFeeAPI(bool value) => - _settingsStore.useMempoolFeeAPI = value; + void setUseMempoolFeeAPI(bool value) => _settingsStore.useMempoolFeeAPI = value; @action void setUsePayjoin(bool value) { diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 2b8452428..3c0d4f48e 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -250,6 +250,7 @@ "digit_pin": "-رقم PIN", "digital_and_physical_card": " بطاقة ائتمان رقمية ومادية مسبقة الدفع", "disable": "إبطال", + "disable_automatic_exchange_status_updates": "تعطيل تحديثات حالة التبادل التلقائي", "disable_bulletin": "تعطيل نشرة حالة الخدمة", "disable_buy": "تعطيل إجراء الشراء", "disable_cake_2fa": "تعطيل 2 عامل المصادقة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 166a47a60..e86a17f95 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -250,6 +250,7 @@ "digit_pin": "-цифрен PIN", "digital_and_physical_card": " дигитална или физическа предплатена дебитна карта", "disable": "Деактивиране", + "disable_automatic_exchange_status_updates": "Деактивирайте актуализациите на състоянието на автоматичния обмен", "disable_bulletin": "Деактивирайте бюлетина за състоянието на услугата", "disable_buy": "Деактивирайте действието за покупка", "disable_cake_2fa": "Деактивирайте Cake 2FA", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 1029e70a6..21a8b2cd4 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -250,6 +250,7 @@ "digit_pin": "-číselný PIN", "digital_and_physical_card": " digitální a fyzické předplacené debetní karty,", "disable": "Zakázat", + "disable_automatic_exchange_status_updates": "Zakázat aktualizace stavu automatické výměny", "disable_bulletin": "Zakázat status servisního stavu", "disable_buy": "Zakázat akci nákupu", "disable_cake_2fa": "Zakázat Cake 2FA", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 023efb7d8..17e75ded0 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -250,6 +250,7 @@ "digit_pin": "-stellige PIN", "digital_and_physical_card": "digitale und physische Prepaid-Debitkarte", "disable": "Deaktivieren", + "disable_automatic_exchange_status_updates": "Deaktivieren Sie die automatischen Austauschstatusaktualisierungen", "disable_bulletin": "Deaktivieren Sie das Bulletin des Service Status", "disable_buy": "Kaufaktion deaktivieren", "disable_cake_2fa": "Cake 2FA deaktivieren", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index bdc89c62b..59533730e 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -250,6 +250,7 @@ "digit_pin": "-digit PIN", "digital_and_physical_card": " digital and physical prepaid debit card", "disable": "Disable", + "disable_automatic_exchange_status_updates": "Disable Automatic Exchange Status Updates", "disable_bulletin": "Disable service status bulletin", "disable_buy": "Disable buy action", "disable_cake_2fa": "Disable Cake 2FA", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 51362512d..5d43cb1a2 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -250,6 +250,7 @@ "digit_pin": "-dígito PIN", "digital_and_physical_card": " tarjeta de débito prepago digital y física", "disable": "Desactivar", + "disable_automatic_exchange_status_updates": "Deshabilitar actualizaciones de estado de intercambio automático", "disable_bulletin": "Desactivar el boletín de estado del servicio", "disable_buy": "Desactivar acción de compra", "disable_cake_2fa": "Desactivar 2FA", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 60411d481..302435dcb 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -250,6 +250,7 @@ "digit_pin": " chiffres", "digital_and_physical_card": "carte de débit prépayée numérique et physique", "disable": "Désactiver", + "disable_automatic_exchange_status_updates": "Désactiver les mises à jour de l'état d'échange automatique", "disable_bulletin": "Désactiver le bulletin de statut de service", "disable_buy": "Désactiver l'action d'achat", "disable_cake_2fa": "Désactiver Cake 2FA", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 2dae85c50..6a3d2f2f0 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -250,6 +250,7 @@ "digit_pin": "-lambar PIN", "digital_and_physical_card": "katin zare kudi na dijital da na zahiri", "disable": "Kashe", + "disable_automatic_exchange_status_updates": "Musaki sabuntawar yanayin canji na atomatik", "disable_bulletin": "Musaki ma'aunin sabis na sabis", "disable_buy": "Kashe alama", "disable_cake_2fa": "Musaki Cake 2FA", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index fc1ffc296..bad13b469 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -250,6 +250,7 @@ "digit_pin": "-अंक पिन", "digital_and_physical_card": "डिजिटल और भौतिक प्रीपेड डेबिट कार्ड", "disable": "अक्षम करना", + "disable_automatic_exchange_status_updates": "स्वचालित एक्सचेंज स्टेटस अपडेट अक्षम करें", "disable_bulletin": "सेवा स्थिति बुलेटिन अक्षम करें", "disable_buy": "खरीद कार्रवाई अक्षम करें", "disable_cake_2fa": "केक 2FA अक्षम करें", @@ -568,8 +569,8 @@ "payjoin_unavailable_sheet_title": "Payjoin अनुपलब्ध क्यों है?", "payment_id": "भुगतान ID: ", "payment_made_easy": "भुगतान आसान किया गया", - "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।", "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", + "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।", "payments": "भुगतान", "pending": " (अपूर्ण)", "percentageOf": "${amount} का", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 3330c52d7..286ced787 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -250,6 +250,7 @@ "digit_pin": "-znamenkasti PIN", "digital_and_physical_card": "digitalna i fizička unaprijed plaćena debitna kartica", "disable": "Onemogući", + "disable_automatic_exchange_status_updates": "Onemogućite ažuriranja automatskog statusa razmjene", "disable_bulletin": "Onemogućite bilten o statusu usluge", "disable_buy": "Onemogući kupnju", "disable_cake_2fa": "Onemogući Cake 2FA", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 3eec382cc..b4d095027 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -250,6 +250,7 @@ "digit_pin": "-նիշ ՊԻՆ", "digital_and_physical_card": " թվային և ֆիզիկական նախավճարային դեբետային քարտ", "disable": "Անջատել", + "disable_automatic_exchange_status_updates": "Անջատեք ավտոմատ փոխանակման կարգավիճակի թարմացումները", "disable_bulletin": "Անջատել ծառայության վիճակի տեղեկագիրը", "disable_buy": "Անջատել գնում գործողությունը", "disable_cake_2fa": "Անջատել Cake 2FA", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 122730d02..d1d699150 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -250,6 +250,7 @@ "digit_pin": "-digit PIN", "digital_and_physical_card": " kartu debit pra-bayar digital dan fisik", "disable": "Cacat", + "disable_automatic_exchange_status_updates": "Nonaktifkan Pembaruan Status Pertukaran Otomatis", "disable_bulletin": "Nonaktifkan Buletin Status Layanan", "disable_buy": "Nonaktifkan tindakan beli", "disable_cake_2fa": "Nonaktifkan Kue 2FA", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 485b79a4d..019885f42 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -250,6 +250,7 @@ "digit_pin": "-cifre PIN", "digital_and_physical_card": "carta di debito prepagata digitale e fisica", "disable": "Disabilita", + "disable_automatic_exchange_status_updates": "Disabilita gli aggiornamenti sullo stato automatico di scambio", "disable_bulletin": "Disabilita bollettino dello stato del servizio", "disable_buy": "Disabilita l'azione di acquisto", "disable_cake_2fa": "Disabilita Cake 2FA", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 717e23ecf..c9021d7da 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -250,6 +250,7 @@ "digit_pin": "桁ピン", "digital_and_physical_card": "デジタルおよび物理プリペイドデビットカード", "disable": "無効にする", + "disable_automatic_exchange_status_updates": "自動交換ステータスの更新を無効にします", "disable_bulletin": "サービスステータス速報を無効にします", "disable_buy": "購入アクションを無効にする", "disable_cake_2fa": "Cake 2FA を無効にする", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e9c332a10..a7ada45e7 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -250,6 +250,7 @@ "digit_pin": "자리 PIN", "digital_and_physical_card": " 디지털 및 실물 선불 직불 카드", "disable": "비활성화", + "disable_automatic_exchange_status_updates": "자동 교환 상태 업데이트를 비활성화합니다", "disable_bulletin": "서비스 상태 게시판 비활성화", "disable_buy": "구매 기능 비활성화", "disable_cake_2fa": "Cake 2FA 비활성화", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index f6d37f8b2..47f7acd20 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -250,6 +250,7 @@ "digit_pin": "-ဂဏန်း PIN", "digital_and_physical_card": " ဒစ်ဂျစ်တယ်နှင့် ရုပ်ပိုင်းဆိုင်ရာ ကြိုတင်ငွေပေးချေသော ဒက်ဘစ်ကတ်", "disable": "ပိတ်ပါ။", + "disable_automatic_exchange_status_updates": "အလိုအလျောက်လဲလှယ် status ကို updates များကို disable လုပ်ပါ", "disable_bulletin": "ဝန်ဆောင်မှုအခြေအနေစာစောင်ကိုပိတ်ပါ", "disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။", "disable_cake_2fa": "ကိတ်မုန့် 2FA ကို ပိတ်ပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 007b2c8f3..41212aa5e 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -250,6 +250,7 @@ "digit_pin": "-cijferige PIN", "digital_and_physical_card": "digitale en fysieke prepaid debetkaart", "disable": "Uitzetten", + "disable_automatic_exchange_status_updates": "Schakel automatische uitwisselingsstatusupdates uit", "disable_bulletin": "Schakel servicestatus Bulletin uit", "disable_buy": "Koopactie uitschakelen", "disable_cake_2fa": "Taart 2FA uitschakelen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index cdc7a3e65..16480285c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -250,6 +250,7 @@ "digit_pin": "-znakowy PIN", "digital_and_physical_card": " cyfrowa i fizyczna przedpłacona karta debetowa", "disable": "Wyłącz", + "disable_automatic_exchange_status_updates": "Wyłącz automatyczne aktualizacje statusu wymiany", "disable_bulletin": "Wyłącz biuletyn", "disable_buy": "Wyłącz akcję kupna", "disable_cake_2fa": "Wyłącz Cake 2FA", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index b151578db..0f2213d2b 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -250,6 +250,7 @@ "digit_pin": "dígitos", "digital_and_physical_card": "cartão de débito pré-pago digital e físico", "disable": "Desativar", + "disable_automatic_exchange_status_updates": "Desativar atualizações automáticas de status de troca", "disable_bulletin": "Desativar boletim de status de serviço", "disable_buy": "Desativar ação de compra", "disable_cake_2fa": "Desabilitar o Cake 2FA", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 329a89f79..bbbc03164 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -250,6 +250,7 @@ "digit_pin": "-значный PIN", "digital_and_physical_card": "цифровая и физическая предоплаченная дебетовая карта", "disable": "Запрещать", + "disable_automatic_exchange_status_updates": "Отключить обновления автоматического статуса обмена", "disable_bulletin": "Отключить бюллетень статуса обслуживания", "disable_buy": "Отключить действие покупки", "disable_cake_2fa": "Отключить торт 2FA", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 45a7d3779..c5b540398 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -250,6 +250,7 @@ "digit_pin": "-หลัก PIN", "digital_and_physical_card": "บัตรเดบิตดิจิตอลและบัตรพื้นฐาน", "disable": "ปิดการใช้งาน", + "disable_automatic_exchange_status_updates": "ปิดใช้งานการอัปเดตสถานะการแลกเปลี่ยนอัตโนมัติ", "disable_bulletin": "ปิดการใช้งาน Bulletin สถานะบริการ", "disable_buy": "ปิดการใช้งานการซื้อ", "disable_cake_2fa": "ปิดการใช้งานเค้ก 2FA", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 4f7a1fd4b..23fedcdda 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -250,6 +250,7 @@ "digit_pin": "-digit PIN", "digital_and_physical_card": " digital at pisikal na prepaid debit card", "disable": "Huwag paganahin", + "disable_automatic_exchange_status_updates": "Huwag paganahin ang mga awtomatikong pag -update ng katayuan ng palitan", "disable_bulletin": "Huwag paganahin ang bulletin ng katayuan ng serbisyo", "disable_buy": "Huwag paganahin ang pagkilos ng pagbili", "disable_cake_2fa": "Huwag paganahin ang Cake 2FA", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index eda0a58bb..9396a9108 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -250,6 +250,7 @@ "digit_pin": " haneli PIN", "digital_and_physical_card": " Dijital para birimleri ile para yükleyebileceğiniz ve ek bilgiye gerek olmayan", "disable": "Devre dışı bırakmak", + "disable_automatic_exchange_status_updates": "Otomatik Değişim Durum Güncellemelerini Devre Dışı Bırak", "disable_bulletin": "Hizmet Durumu Bültenini Devre Dışı Bırak", "disable_buy": "Satın alma işlemini devre dışı bırak", "disable_cake_2fa": "Cake 2FA'yı Devre Dışı Bırak", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 6ed48398b..07e38ff3b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -250,6 +250,7 @@ "digit_pin": "-значний PIN", "digital_and_physical_card": " цифрова та фізична передплачена дебетова картка", "disable": "Вимкнути", + "disable_automatic_exchange_status_updates": "Вимкнути автоматичні оновлення стану обміну", "disable_bulletin": "Вимкнути статус послуги", "disable_buy": "Вимкнути дію покупки", "disable_cake_2fa": "Вимкнути Cake 2FA", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index a62292fd6..9e4676d13 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -250,6 +250,7 @@ "digit_pin": "-ہندسوں کا پن", "digital_and_physical_card": " ڈیجیٹل اور فزیکل پری پیڈ ڈیبٹ کارڈ", "disable": "غیر فعال کریں۔", + "disable_automatic_exchange_status_updates": "خودکار تبادلہ کی حیثیت کی تازہ کاریوں کو غیر فعال کریں", "disable_bulletin": "خدمت کی حیثیت کا بلیٹن کو غیر فعال کریں", "disable_buy": "خرید ایکشن کو غیر فعال کریں۔", "disable_cake_2fa": "کیک 2FA کو غیر فعال کریں۔", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index aa36d4490..157232156 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -249,6 +249,7 @@ "digit_pin": "Mã PIN - số", "digital_and_physical_card": "thẻ ghi nợ trả trước kỹ thuật số và vật lý", "disable": "Vô hiệu hóa", + "disable_automatic_exchange_status_updates": "Tắt các bản cập nhật trạng thái trao đổi tự động", "disable_bulletin": "Vô hiệu hóa bản tin tình trạng dịch vụ", "disable_buy": "Vô hiệu hóa chức năng mua", "disable_cake_2fa": "Vô hiệu hóa 2FA Cake", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 1e660fa3c..2ab9c0a42 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -250,6 +250,7 @@ "digit_pin": "-díjíìtì òǹkà ìdánimọ̀ àdáni", "digital_and_physical_card": " káàdì ìrajà t'ara àti ti ayélujára", "disable": "Ko si", + "disable_automatic_exchange_status_updates": "Mu awọn imudojuiwọn ipo paṣipaarọ aifọwọyi", "disable_bulletin": "Mu blogti ipo ipo ṣiṣẹ", "disable_buy": "Ko iṣọrọ ọja", "disable_cake_2fa": "Ko 2FA Cake sii", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 8b5e7cd0a..e1b71508f 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -250,6 +250,7 @@ "digit_pin": "位 PIN", "digital_and_physical_card": "数字和物理预付借记卡", "disable": "停用", + "disable_automatic_exchange_status_updates": "禁用自动交换状态更新", "disable_bulletin": "禁用服务状态公告", "disable_buy": "禁用购买操作", "disable_cake_2fa": "禁用蛋糕 2FA",