CW-959: Swap Status on Transaction Screen (#2299)

* feat(swap-status-monitor): add real-time swap status monitoring and UI updates - Introduce SwapManager for automatic tracking of active-wallet swaps. - Automatically queues new or updated trades from the Hive box. - Periodically fetch and persist swap statuses via the corresponding trade provider. - Implement start(wallet, providers), stop(), and dispose() for lifecycle control. - Apply user's ExchangeApiMode(disabled, tor-only, enabled) when fetching updates. - Remove swaps from the watchlist on any final state (completed, expired, failed). - Dispose SwapManager in AppState.dispose() to cancel polling and the Hive subscription. * refactor(swap-status): replace SwapManager with TradeMonitor for improved trade monitoring. This change improves the flow by simplifying the trade monitoring logic. - Removes SwapManager class and replace with TradeMonitor implementation - Update di and Appstate to register and dispose TradeMonitor - Modify DashboardViewModel to use TradeMonitor instead of SwapManager * fix: Modify trade monitoring logic to ensure trade timers are properly disposed when wallet switching occurs * fix(swap-status): Fix receive amount for exchanges showing as .00 because of null values * feat(swap-status): Enhance Trade Monitoring This change: - Adds a privacy settings option to disable automatic exchange status updates. - Prevents trade monitoring when privacy settings option is enabled. - Disables trade monitoring when the app is in background, we only want to run these checks in foreground. - Refactors the trade monitoring logic to remove unneccessary checks and use of resources. * feat(swap-status): Enhance Trade Monitoring This change: - Adds a privacy settings option to disable automatic exchange status updates. - Prevents trade monitoring when privacy settings option is enabled. - Disables trade monitoring when the app is in background, we only want to run these checks in foreground. - Refactors the trade monitoring logic to remove unneccessary checks and use of resources. * fix(swap-staus): Prevent unneccessary calls * feat(swap-status): Prevent api request calls as long as last update time is less than specified interval

David Adegoke committed Jun 4, 2025 at 16:24 UTC 1d6e594e045c4871cfcddd0e494ce3db10c90e80
39 files changed +455 -82
lib/core/trade_monitor.dart new
+247
@@ -0,0 +1,247 @@
1 +import 'dart:async';
2 +import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
3 +import 'package:cake_wallet/exchange/trade.dart';
4 +import 'package:cake_wallet/exchange/trade_state.dart';
5 +import 'package:cake_wallet/store/dashboard/trades_store.dart';
6 +import 'package:cake_wallet/entities/exchange_api_mode.dart';
7 +import 'package:cake_wallet/exchange/exchange_provider_description.dart';
8 +import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
9 +import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
10 +import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
11 +import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
12 +import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart';
13 +import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
14 +import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
15 +import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
16 +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
17 +import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
18 +import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
19 +import 'package:cw_core/utils/print_verbose.dart';
20 +import 'package:hive/hive.dart';
21 +import 'package:cake_wallet/store/app_store.dart';
22 +import 'package:shared_preferences/shared_preferences.dart';
23 +
24 +class TradeMonitor {
25 + static const int _tradeCheckIntervalMinutes = 5;
26 + static const int _maxTradeAgeHours = 24;
27 +
28 + TradeMonitor({
29 + required this.tradesStore,
30 + required this.trades,
31 + required this.appStore,
32 + required this.preferences,
33 + });
34 +
35 + final TradesStore tradesStore;
36 + final Box<Trade> trades;
37 + final AppStore appStore;
38 + final Map<String, Timer> _tradeTimers = {};
39 + final SharedPreferences preferences;
40 +
41 + ExchangeProvider? _getProviderByDescription(ExchangeProviderDescription description) {
42 + switch (description) {
43 + case ExchangeProviderDescription.changeNow:
44 + return ChangeNowExchangeProvider(settingsStore: appStore.settingsStore);
45 + case ExchangeProviderDescription.sideShift:
46 + return SideShiftExchangeProvider();
47 + case ExchangeProviderDescription.simpleSwap:
48 + return SimpleSwapExchangeProvider();
49 + case ExchangeProviderDescription.trocador:
50 + return TrocadorExchangeProvider();
51 + case ExchangeProviderDescription.exolix:
52 + return ExolixExchangeProvider();
53 + case ExchangeProviderDescription.thorChain:
54 + return ThorChainExchangeProvider(tradesStore: trades);
55 + case ExchangeProviderDescription.swapTrade:
56 + return SwapTradeExchangeProvider();
57 + case ExchangeProviderDescription.letsExchange:
58 + return LetsExchangeExchangeProvider();
59 + case ExchangeProviderDescription.stealthEx:
60 + return StealthExExchangeProvider();
61 + case ExchangeProviderDescription.chainflip:
62 + return ChainflipExchangeProvider(tradesStore: trades);
63 + case ExchangeProviderDescription.xoSwap:
64 + return XOSwapExchangeProvider();
65 + }
66 + return null;
67 + }
68 +
69 + void monitorActiveTrades(String walletId) {
70 + // Checks if the trade monitoring is permitted
71 + // i.e the user has not disabled the exchange api mode or the status updates
72 + final isTradeMonitoringPermitted = _isTradeMonitoringPermitted();
73 + if (!isTradeMonitoringPermitted) {
74 + return;
75 + }
76 +
77 + final trades = tradesStore.trades;
78 + final tradesToCancel = <String>[];
79 +
80 + for (final item in trades) {
81 + final trade = item.trade;
82 +
83 + final provider = _getProviderByDescription(trade.provider);
84 +
85 + // Multiple checks to see if to skip the trade, if yes, we cancel the timer if it exists
86 + if (_shouldSkipTrade(trade, walletId, provider)) {
87 + tradesToCancel.add(trade.id);
88 + continue;
89 + }
90 +
91 + if (_tradeTimers.containsKey(trade.id)) {
92 + printV('Trade ${trade.id} is already being monitored');
93 + continue;
94 + } else {
95 + _startTradeMonitoring(trade, provider!);
96 + }
97 + }
98 +
99 + // After going through the list of available trades, we cancel the timers in the tradesToCancel list
100 + _cancelMultipleTradeTimers(tradesToCancel);
101 + }
102 +
103 + bool _isTradeMonitoringPermitted() {
104 + final disableAutomaticExchangeStatusUpdates =
105 + appStore.settingsStore.disableAutomaticExchangeStatusUpdates;
106 + if (disableAutomaticExchangeStatusUpdates) {
107 + printV('Automatic exchange status updates are disabled');
108 + return false;
109 + }
110 +
111 + final exchangeApiMode = appStore.settingsStore.exchangeStatus;
112 + if (exchangeApiMode == ExchangeApiMode.disabled) {
113 + printV('Exchange API mode is disabled');
114 + return false;
115 + }
116 +
117 + return true;
118 + }
119 +
120 + bool _shouldSkipTrade(Trade trade, String walletId, ExchangeProvider? provider) {
121 + if (trade.walletId != walletId) {
122 + printV('Skipping trade ${trade.id} because it\'s not for this wallet');
123 + return true;
124 + }
125 +
126 + final createdAt = trade.createdAt;
127 + if (createdAt == null) {
128 + printV('Skipping trade ${trade.id} because it has no createdAt');
129 + return true;
130 + }
131 +
132 + if (DateTime.now().difference(createdAt).inHours > _maxTradeAgeHours) {
133 + printV('Skipping trade ${trade.id} because it\'s older than ${_maxTradeAgeHours} hours');
134 + return true;
135 + }
136 +
137 + if (_isFinalState(trade.state)) {
138 + printV('Skipping trade ${trade.id} because it\'s in a final state');
139 + return true;
140 + }
141 +
142 + if (provider == null) {
143 + printV('Skipping trade ${trade.id} because the provider is not supported');
144 + return true;
145 + }
146 +
147 + if (appStore.settingsStore.exchangeStatus == ExchangeApiMode.torOnly &&
148 + !provider.supportsOnionAddress) {
149 + printV('Skipping ${provider.description}, no TOR support');
150 + return true;
151 + }
152 +
153 + return false;
154 + }
155 +
156 + void _startTradeMonitoring(Trade trade, ExchangeProvider provider) {
157 + final timer = Timer.periodic(
158 + Duration(minutes: _tradeCheckIntervalMinutes),
159 + (_) => _checkTradeStatus(trade, provider),
160 + );
161 +
162 + _checkTradeStatus(trade, provider);
163 +
164 + _tradeTimers[trade.id] = timer;
165 + }
166 +
167 + Future<void> _checkTradeStatus(Trade trade, ExchangeProvider provider) async {
168 + final lastUpdatedAtFromPrefs = preferences.getString('trade_${trade.id}_updated_at');
169 +
170 + if (lastUpdatedAtFromPrefs != null) {
171 + final lastUpdatedAtDateTime = DateTime.parse(lastUpdatedAtFromPrefs);
172 + final timeSinceLastUpdate = DateTime.now().difference(lastUpdatedAtDateTime).inMinutes;
173 +
174 + if (timeSinceLastUpdate < _tradeCheckIntervalMinutes) {
175 + printV(
176 + 'Skipping trade ${trade.id} status update check because it was updated less than ${_tradeCheckIntervalMinutes} minutes ago ($timeSinceLastUpdate minutes ago)',
177 + );
178 + return;
179 + }
180 + }
181 +
182 + try {
183 + final updated = await provider.findTradeById(id: trade.id);
184 + trade
185 + ..stateRaw = updated.state.raw
186 + ..receiveAmount = updated.receiveAmount ?? trade.receiveAmount
187 + ..outputTransaction = updated.outputTransaction ?? trade.outputTransaction;
188 + printV('Trade ${trade.id} updated: ${trade.state}');
189 + await trade.save();
190 +
191 + await preferences.setString('trade_${trade.id}_updated_at', DateTime.now().toIso8601String());
192 + printV('Trade ${trade.id} updated at: ${DateTime.now().toIso8601String()}');
193 +
194 + // If the updated trade is in a final state, we cancel the timer
195 + if (_isFinalState(updated.state)) {
196 + printV('Trade ${trade.id} is in final state');
197 + _cancelSingleTradeTimer(trade.id);
198 + }
199 + } catch (e) {
200 + printV('Error fetching status for ${trade.id}: $e');
201 + }
202 + }
203 +
204 + bool _isFinalState(TradeState state) {
205 + return {
206 + TradeState.completed.raw,
207 + TradeState.success.raw,
208 + TradeState.confirmed.raw,
209 + TradeState.settled.raw,
210 + TradeState.finished.raw,
211 + TradeState.expired.raw,
212 + TradeState.failed.raw,
213 + TradeState.notFound.raw,
214 + }.contains(state.raw);
215 + }
216 +
217 + void _cancelSingleTradeTimer(String tradeId) {
218 + if (_tradeTimers.containsKey(tradeId)) {
219 + _tradeTimers[tradeId]?.cancel();
220 + _tradeTimers.remove(tradeId);
221 + printV('Trade timer for ${tradeId} cancelled');
222 + }
223 + }
224 +
225 + void _cancelMultipleTradeTimers(List<String> tradeIds) {
226 + for (final tradeId in tradeIds) {
227 + _cancelSingleTradeTimer(tradeId);
228 + }
229 + }
230 +
231 + /// This is called when the app is brought back to foreground.
232 + void resumeTradeMonitoring() {
233 + if (appStore.wallet != null) {
234 + monitorActiveTrades(appStore.wallet!.id);
235 + }
236 + }
237 +
238 + /// There's no need to run the trade checks when the app is in background.
239 + /// We only want to update the trade status when the app is in foreground.
240 + /// This helps to reduce the battery usage, network usage and enhance overall privacy.
241 + ///
242 + /// This is called when the app is sent to background or when the app is closed.
243 + void stopTradeMonitoring() {
244 + printV('Stopping trade monitoring');
245 + _cancelMultipleTradeTimers(_tradeTimers.keys.toList());
246 + }
247 +}
lib/di.dart
+36 -25
@@ -275,6 +275,7 @@ import 'src/screens/buy/buy_sell_page.dart';
275 import 'cake_pay/cake_pay_payment_credantials.dart';
276 import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart';
277 import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
278 +import 'package:cake_wallet/core/trade_monitor.dart';
279
280 final getIt = GetIt.instance;
281
@@ -507,19 +508,42 @@ Future<void> setup({
508 settingsStore: getIt.get<SettingsStore>(),
509 fiatConvertationStore: getIt.get<FiatConversionStore>()));
510
510 - getIt.registerFactory(() => DashboardViewModel(
511 - balanceViewModel: getIt.get<BalanceViewModel>(),
512 - appStore: getIt.get<AppStore>(),
511 + getIt.registerFactory(
512 + () => ExchangeViewModel(
513 + getIt.get<AppStore>(),
514 + _tradesSource,
515 + getIt.get<ExchangeTemplateStore>(),
516 + getIt.get<TradesStore>(),
517 + getIt.get<AppStore>().settingsStore,
518 + getIt.get<SharedPreferences>(),
519 + getIt.get<ContactListViewModel>(),
520 + getIt.get<FeesViewModel>(),
521 + ),
522 + );
523 +
524 + getIt.registerSingleton(
525 + TradeMonitor(
526 tradesStore: getIt.get<TradesStore>(),
514 - tradeFilterStore: getIt.get<TradeFilterStore>(),
515 - transactionFilterStore: getIt.get<TransactionFilterStore>(),
516 - settingsStore: settingsStore,
517 - yatStore: getIt.get<YatStore>(),
518 - ordersStore: getIt.get<OrdersStore>(),
519 - anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
520 - payjoinTransactionsStore: getIt.get<PayjoinTransactionsStore>(),
521 - sharedPreferences: getIt.get<SharedPreferences>(),
522 - keyService: getIt.get<KeyService>()));
527 + trades: _tradesSource,
528 + appStore: getIt.get<AppStore>(),
529 + preferences: getIt.get<SharedPreferences>(),
530 + ),
531 + );
532 +
533 + getIt.registerFactory(() => DashboardViewModel(
534 + tradeMonitor: getIt.get<TradeMonitor>(),
535 + balanceViewModel: getIt.get<BalanceViewModel>(),
536 + appStore: getIt.get<AppStore>(),
537 + tradesStore: getIt.get<TradesStore>(),
538 + tradeFilterStore: getIt.get<TradeFilterStore>(),
539 + transactionFilterStore: getIt.get<TransactionFilterStore>(),
540 + settingsStore: settingsStore,
541 + yatStore: getIt.get<YatStore>(),
542 + ordersStore: getIt.get<OrdersStore>(),
543 + anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
544 + payjoinTransactionsStore: getIt.get<PayjoinTransactionsStore>(),
545 + sharedPreferences: getIt.get<SharedPreferences>(),
546 + keyService: getIt.get<KeyService>()));
547
548 getIt.registerFactory<AuthService>(
549 () => AuthService(
@@ -1051,19 +1075,6 @@ Future<void> setup({
1075
1076 getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
1077
1054 - getIt.registerFactory(
1055 - () => ExchangeViewModel(
1056 - getIt.get<AppStore>(),
1057 - _tradesSource,
1058 - getIt.get<ExchangeTemplateStore>(),
1059 - getIt.get<TradesStore>(),
1060 - getIt.get<SettingsStore>(),
1061 - getIt.get<SharedPreferences>(),
1062 - getIt.get<ContactListViewModel>(),
1063 - getIt.get<FeesViewModel>(),
1064 - ),
1065 - );
1066 -
1078 getIt.registerFactory<FeesViewModel>(
1079 () => FeesViewModel(
1080 getIt.get<AppStore>(),
lib/entities/preferences_key.dart
+1
@@ -24,6 +24,7 @@ class PreferencesKey {
24 static const shouldSaveRecipientAddressKey = 'save_recipient_address';
25 static const isAppSecureKey = 'is_app_secure';
26 static const disableTradeOption = 'disable_buy';
27 + static const disableAutomaticExchangeStatusUpdates = 'disable_automatic_exchange_status_updates';
28 static const disableBulletinKey = 'disable_bulletin';
29 static const walletListOrder = 'wallet_list_order';
30 static const contactListOrder = 'contact_list_order';
lib/main.dart
+3
@@ -50,6 +50,7 @@ import 'package:cw_core/root_dir.dart';
50 import 'package:shared_preferences/shared_preferences.dart';
51 import 'package:cw_core/window_size.dart';
52 import 'package:logging/logging.dart';
53 +import 'package:cake_wallet/core/trade_monitor.dart';
54
55 final navigatorKey = GlobalKey<NavigatorState>();
56 final rootKey = GlobalKey<RootState>();
@@ -297,6 +298,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
298 final appStore = getIt.get<AppStore>();
299 final authService = getIt.get<AuthService>();
300 final linkViewModel = getIt.get<LinkViewModel>();
301 + final tradeMonitor = getIt.get<TradeMonitor>();
302 final statusBarColor = Colors.transparent;
303 final authenticationStore = getIt.get<AuthenticationStore>();
304 final initialRoute = authenticationStore.state == AuthenticationState.uninitialized
@@ -317,6 +319,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
319 navigatorKey: navigatorKey,
320 authService: authService,
321 linkViewModel: linkViewModel,
322 + tradeMonitor: tradeMonitor,
323 child: ThemeProvider(
324 themeStore: appStore.themeStore,
325 materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
lib/src/screens/dashboard/pages/transactions_page.dart
+12 -10
@@ -166,17 +166,19 @@ class TransactionsPage extends StatelessWidget {
166
167 return Observer(
168 builder: (_) => TradeRow(
169 - key: item.key,
170 - onTap: () => Navigator.of(context)
171 - .pushNamed(Routes.tradeDetails, arguments: trade),
169 + key: item.key,
170 + onTap: () => Navigator.of(context)
171 + .pushNamed(Routes.tradeDetails, arguments: trade),
172 + swapState: trade.state,
173 provider: trade.provider,
173 - from: trade.from,
174 - to: trade.to,
175 - createdAtFormattedDate: trade.createdAt != null
176 - ? DateFormat('HH:mm').format(trade.createdAt!)
177 - : null,
178 - formattedAmount: item.tradeFormattedAmount,
179 - formattedReceiveAmount: item.tradeFormattedReceiveAmount),
174 + from: trade.from,
175 + to: trade.to,
176 + createdAtFormattedDate: trade.createdAt != null
177 + ? DateFormat('HH:mm').format(trade.createdAt!)
178 + : null,
179 + formattedAmount: item.tradeFormattedAmount,
180 + formattedReceiveAmount: item.tradeFormattedReceiveAmount
181 + ),
182 );
183 }
184 if (item is OrderListItem) {
lib/src/screens/dashboard/widgets/trade_row.dart
+54 -19
@@ -1,7 +1,9 @@
1 +import 'package:cake_wallet/exchange/trade_state.dart';
2 +import 'package:cake_wallet/palette.dart';
3 import 'package:cake_wallet/utils/image_utill.dart';
4 import 'package:flutter/material.dart';
3 -import 'package:cw_core/crypto_currency.dart';
5 import 'package:cake_wallet/exchange/exchange_provider_description.dart';
6 +import 'package:cw_core/crypto_currency.dart';
7
8 class TradeRow extends StatelessWidget {
9 TradeRow({
@@ -12,6 +14,7 @@ class TradeRow extends StatelessWidget {
14 this.onTap,
15 this.formattedAmount,
16 this.formattedReceiveAmount,
17 + required this.swapState,
18 super.key,
19 });
20
@@ -22,6 +25,7 @@ class TradeRow extends StatelessWidget {
25 final String? createdAtFormattedDate;
26 final String? formattedAmount;
27 final String? formattedReceiveAmount;
28 + final TradeState swapState;
29
30 @override
31 Widget build(BuildContext context) {
@@ -29,25 +33,39 @@ class TradeRow extends StatelessWidget {
33 final receiveAmountCrypto = to.toString();
34
35 return InkWell(
32 - onTap: onTap,
33 - child: Container(
34 - padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
35 - color: Colors.transparent,
36 - child: Row(
37 - mainAxisSize: MainAxisSize.max,
38 - crossAxisAlignment: CrossAxisAlignment.center,
39 - children: [
40 - ClipRRect(
41 - borderRadius: BorderRadius.circular(50),
42 - child: ImageUtil.getImageFromPath(
43 - imagePath: provider.image,
44 - height: 36,
45 - width: 36,
36 + onTap: onTap,
37 + child: Container(
38 + padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
39 + color: Colors.transparent,
40 + child: Row(
41 + mainAxisSize: MainAxisSize.max,
42 + crossAxisAlignment: CrossAxisAlignment.center,
43 + children: [
44 + Stack(
45 + clipBehavior: Clip.none,
46 + children: [
47 + ClipRRect(
48 + borderRadius: BorderRadius.circular(50),
49 + child: ImageUtil.getImageFromPath(
50 + imagePath: provider.image, height: 36, width: 36),),
51 + Positioned(
52 + right: 0,
53 + bottom: 2,
54 + child: Container(
55 + height: 8,
56 + width: 8,
57 + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
58 + decoration: BoxDecoration(
59 + color: _statusColor(context, swapState),
60 + borderRadius: BorderRadius.circular(12),
61 + ),
62 + ),
63 + ),
64 + ],
65 ),
47 - ),
48 - SizedBox(width: 12),
49 - Expanded(
50 - child: Column(
66 + SizedBox(width: 12),
67 + Expanded(
68 + child: Column(
69 mainAxisSize: MainAxisSize.min,
70 children: [
71 Row(
@@ -104,4 +122,21 @@ class TradeRow extends StatelessWidget {
122 ),
123 );
124 }
125 +
126 + Color _statusColor(BuildContext context, TradeState status) {
127 + switch (status) {
128 + case TradeState.complete:
129 + case TradeState.completed:
130 + case TradeState.finished:
131 + case TradeState.success:
132 + case TradeState.settled:
133 + return PaletteDark.brightGreen;
134 + case TradeState.failed:
135 + case TradeState.expired:
136 + case TradeState.notFound:
137 + return Palette.darkRed;
138 + default:
139 + return const Color(0xffff6600);
140 + }
141 + }
142 }
lib/src/screens/root/root.dart
+7
@@ -3,6 +3,7 @@ import 'dart:io';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/core/auth_service.dart';
5 import 'package:cake_wallet/core/totp_request_details.dart';
6 +import 'package:cake_wallet/core/trade_monitor.dart';
7 import 'package:cake_wallet/utils/device_info.dart';
8 import 'package:cake_wallet/view_model/link_view_model.dart';
9 import 'package:cw_core/utils/print_verbose.dart';
@@ -27,6 +28,7 @@ class Root extends StatefulWidget {
28 required this.navigatorKey,
29 required this.authService,
30 required this.linkViewModel,
31 + required this.tradeMonitor,
32 }) : super(key: key);
33
34 final AuthenticationStore authenticationStore;
@@ -35,6 +37,7 @@ class Root extends StatefulWidget {
37 final AuthService authService;
38 final Widget child;
39 final LinkViewModel linkViewModel;
40 + final TradeMonitor tradeMonitor;
41
42 @override
43 RootState createState() => RootState();
@@ -141,6 +144,8 @@ class RootState extends State<Root> with WidgetsBindingObserver {
144 bitcoin!.stopPayjoinSessions(widget.appStore.wallet!);
145 }
146
147 + widget.tradeMonitor.stopTradeMonitoring();
148 +
149 break;
150 case AppLifecycleState.resumed:
151 widget.authService.requireAuth().then((value) {
@@ -154,6 +159,8 @@ class RootState extends State<Root> with WidgetsBindingObserver {
159 widget.appStore.settingsStore.usePayjoin) {
160 bitcoin!.resumePayjoinSessions(widget.appStore.wallet!);
161 }
162 +
163 + widget.tradeMonitor.resumeTradeMonitoring();
164 break;
165 default:
166 break;
lib/src/screens/settings/privacy_page.dart
+31 -20
@@ -75,30 +75,41 @@ class PrivacyPage extends BasePage {
75 ),
76 if (DeviceInfo.instance.isMobile)
77 SettingsSwitcherCell(
78 - title: S.current.prevent_screenshots,
79 - value: _privacySettingsViewModel.isAppSecure,
80 - onValueChange: (BuildContext _, bool value) {
81 - _privacySettingsViewModel.setIsAppSecure(value);
82 - }),
83 - SettingsSwitcherCell(
84 - title: S.current.disable_buy,
85 - value: _privacySettingsViewModel.disableTradeOption,
78 + title: S.current.prevent_screenshots,
79 + value: _privacySettingsViewModel.isAppSecure,
80 onValueChange: (BuildContext _, bool value) {
87 - _privacySettingsViewModel.setDisableTradeOption(value);
88 - }),
81 + _privacySettingsViewModel.setIsAppSecure(value);
82 + },
83 + ),
84 SettingsSwitcherCell(
90 - title: S.current.disable_bulletin,
91 - value: _privacySettingsViewModel.disableBulletin,
92 - onValueChange: (BuildContext _, bool value) {
93 - _privacySettingsViewModel.setDisableBulletin(value);
94 - }),
85 + title: S.current.disable_buy,
86 + value: _privacySettingsViewModel.disableTradeOption,
87 + onValueChange: (BuildContext _, bool value) {
88 + _privacySettingsViewModel.setDisableTradeOption(value);
89 + },
90 + ),
91 + SettingsSwitcherCell(
92 + title: S.current.disable_automatic_exchange_status_updates,
93 + value: _privacySettingsViewModel.disableAutomaticExchangeStatusUpdates,
94 + onValueChange: (BuildContext _, bool value) {
95 + _privacySettingsViewModel.setDisableAutomaticExchangeStatusUpdates(value);
96 + },
97 + ),
98 + SettingsSwitcherCell(
99 + title: S.current.disable_bulletin,
100 + value: _privacySettingsViewModel.disableBulletin,
101 + onValueChange: (BuildContext _, bool value) {
102 + _privacySettingsViewModel.setDisableBulletin(value);
103 + },
104 + ),
105 if (_privacySettingsViewModel.canUseEtherscan)
106 SettingsSwitcherCell(
97 - title: S.current.etherscan_history,
98 - value: _privacySettingsViewModel.useEtherscan,
99 - onValueChange: (BuildContext _, bool value) {
100 - _privacySettingsViewModel.setUseEtherscan(value);
101 - }),
107 + title: S.current.etherscan_history,
108 + value: _privacySettingsViewModel.useEtherscan,
109 + onValueChange: (BuildContext _, bool value) {
110 + _privacySettingsViewModel.setUseEtherscan(value);
111 + },
112 + ),
113 if (_privacySettingsViewModel.canUsePolygonScan)
114 SettingsSwitcherCell(
115 title: S.current.polygonscan_history,
lib/store/settings_store.dart
+11
@@ -64,6 +64,7 @@ abstract class SettingsStoreBase with Store {
64 required NanoSeedType initialNanoSeedType,
65 required bool initialAppSecure,
66 required bool initialDisableTrade,
67 + required bool initialDisableAutomaticExchangeStatusUpdates,
68 required FilterListOrderType initialWalletListOrder,
69 required FilterListOrderType initialContactListOrder,
70 required bool initialDisableBulletin,
@@ -156,6 +157,7 @@ abstract class SettingsStoreBase with Store {
157 numberOfFailedTokenTrials = initialFailedTokenTrial,
158 isAppSecure = initialAppSecure,
159 disableTradeOption = initialDisableTrade,
160 + disableAutomaticExchangeStatusUpdates = initialDisableAutomaticExchangeStatusUpdates,
161 disableBulletin = initialDisableBulletin,
162 walletListOrder = initialWalletListOrder,
163 contactListOrder = initialContactListOrder,
@@ -307,6 +309,9 @@ abstract class SettingsStoreBase with Store {
309 reaction((_) => disableTradeOption,
310 (bool disableTradeOption) => sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption));
311
312 + reaction((_) => disableAutomaticExchangeStatusUpdates,
313 + (bool disableAutomaticExchangeStatusUpdates) => sharedPreferences.setBool(PreferencesKey.disableAutomaticExchangeStatusUpdates, disableAutomaticExchangeStatusUpdates));
314 +
315 reaction(
316 (_) => disableBulletin,
317 (bool disableBulletin) =>
@@ -675,6 +680,9 @@ abstract class SettingsStoreBase with Store {
680 @observable
681 bool disableTradeOption;
682
683 + @observable
684 + bool disableAutomaticExchangeStatusUpdates;
685 +
686 @observable
687 FilterListOrderType contactListOrder;
688
@@ -956,6 +964,7 @@ abstract class SettingsStoreBase with Store {
964 sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false;
965 final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false;
966 final disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? false;
967 + final disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? false;
968 final disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? false;
969 final walletListOrder =
970 FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
@@ -1273,6 +1282,7 @@ abstract class SettingsStoreBase with Store {
1282 initialNanoSeedType: nanoSeedType,
1283 initialAppSecure: isAppSecure,
1284 initialDisableTrade: disableTradeOption,
1285 + initialDisableAutomaticExchangeStatusUpdates: disableAutomaticExchangeStatusUpdates,
1286 initialDisableBulletin: disableBulletin,
1287 initialWalletListOrder: walletListOrder,
1288 initialWalletListAscending: walletListAscending,
@@ -1436,6 +1446,7 @@ abstract class SettingsStoreBase with Store {
1446 sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
1447 isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
1448 disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? disableTradeOption;
1449 + disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? disableAutomaticExchangeStatusUpdates;
1450 disableBulletin =
1451 sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
1452 walletListOrder =
lib/view_model/dashboard/dashboard_view_model.dart
+12 -1
@@ -23,7 +23,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
23 import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
24 import 'package:cake_wallet/store/settings_store.dart';
25 import 'package:cake_wallet/store/yat/yat_store.dart';
26 -import 'package:cake_wallet/themes/core/material_base_theme.dart';
26 import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
27 import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
28 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
@@ -56,6 +55,8 @@ import 'package:mobx/mobx.dart';
55 import 'package:permission_handler/permission_handler.dart';
56 import 'package:shared_preferences/shared_preferences.dart';
57
58 +import 'package:cake_wallet/core/trade_monitor.dart';
59 +
60 part 'dashboard_view_model.g.dart';
61
62 class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel;
@@ -63,6 +64,7 @@ class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel;
64 abstract class DashboardViewModelBase with Store {
65 DashboardViewModelBase(
66 {required this.balanceViewModel,
67 + required this.tradeMonitor,
68 required this.appStore,
69 required this.tradesStore,
70 required this.tradeFilterStore,
@@ -270,6 +272,9 @@ abstract class DashboardViewModelBase with Store {
272 _checkMweb();
273 showDecredInfoCard = wallet?.type == WalletType.decred &&
274 sharedPreferences.getBool(PreferencesKey.showDecredInfoCard) != false;
275 +
276 + tradeMonitor.stopTradeMonitoring();
277 + tradeMonitor.monitorActiveTrades(wallet!.id);
278 });
279
280 _transactionDisposer?.reaction.dispose();
@@ -298,6 +303,10 @@ abstract class DashboardViewModelBase with Store {
303
304 _checkMweb();
305 reaction((_) => settingsStore.mwebAlwaysScan, (bool value) => _checkMweb());
306 +
307 + reaction((_) => tradesStore.trades, (_) => tradeMonitor.monitorActiveTrades(wallet.id));
308 +
309 + tradeMonitor.monitorActiveTrades(wallet.id);
310 }
311
312 bool _isTransactionDisposerCallbackRunning = false;
@@ -773,6 +782,8 @@ abstract class DashboardViewModelBase with Store {
782
783 BalanceViewModel balanceViewModel;
784
785 + TradeMonitor tradeMonitor;
786 +
787 AppStore appStore;
788
789 SettingsStore settingsStore;
lib/view_model/settings/privacy_settings_view_model.dart
+12 -6
@@ -33,9 +33,8 @@ abstract class PrivacySettingsViewModelBase with Store {
33 @action
34 void setAutoGenerateSubaddresses(bool value) {
35 _wallet.isEnabledAutoGenerateSubaddress = value;
36 - _settingsStore.autoGenerateSubaddressStatus = value
37 - ? AutoGenerateSubaddressStatus.enabled
38 - : AutoGenerateSubaddressStatus.disabled;
36 + _settingsStore.autoGenerateSubaddressStatus =
37 + value ? AutoGenerateSubaddressStatus.enabled : AutoGenerateSubaddressStatus.disabled;
38 }
39
40 bool get isAutoGenerateSubaddressesVisible => [
@@ -61,6 +60,10 @@ abstract class PrivacySettingsViewModelBase with Store {
60 @computed
61 bool get disableTradeOption => _settingsStore.disableTradeOption;
62
63 + @computed
64 + bool get disableAutomaticExchangeStatusUpdates =>
65 + _settingsStore.disableAutomaticExchangeStatusUpdates;
66 +
67 @computed
68 bool get disableBulletin => _settingsStore.disableBulletin;
69
@@ -129,6 +132,10 @@ abstract class PrivacySettingsViewModelBase with Store {
132 @action
133 void setDisableTradeOption(bool value) => _settingsStore.disableTradeOption = value;
134
135 + @action
136 + void setDisableAutomaticExchangeStatusUpdates(bool value) =>
137 + _settingsStore.disableAutomaticExchangeStatusUpdates = value;
138 +
139 @action
140 void setDisableBulletin(bool value) => _settingsStore.disableBulletin = value;
141
@@ -146,7 +153,7 @@ abstract class PrivacySettingsViewModelBase with Store {
153
154 @action
155 void setLookupsWellKnown(bool value) => _settingsStore.lookupsWellKnown = value;
149 -
156 +
157 @action
158 void setLookupsYatService(bool value) => _settingsStore.lookupsYatService = value;
159
@@ -175,8 +182,7 @@ abstract class PrivacySettingsViewModelBase with Store {
182 }
183
184 @action
178 - void setUseMempoolFeeAPI(bool value) =>
179 - _settingsStore.useMempoolFeeAPI = value;
185 + void setUseMempoolFeeAPI(bool value) => _settingsStore.useMempoolFeeAPI = value;
186
187 @action
188 void setUsePayjoin(bool value) {
res/values/strings_ar.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-رقم PIN",
251 "digital_and_physical_card": " بطاقة ائتمان رقمية ومادية مسبقة الدفع",
252 "disable": "إبطال",
253 + "disable_automatic_exchange_status_updates": "تعطيل تحديثات حالة التبادل التلقائي",
254 "disable_bulletin": "تعطيل نشرة حالة الخدمة",
255 "disable_buy": "تعطيل إجراء الشراء",
256 "disable_cake_2fa": "تعطيل 2 عامل المصادقة",
res/values/strings_bg.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-цифрен PIN",
251 "digital_and_physical_card": " дигитална или физическа предплатена дебитна карта",
252 "disable": "Деактивиране",
253 + "disable_automatic_exchange_status_updates": "Деактивирайте актуализациите на състоянието на автоматичния обмен",
254 "disable_bulletin": "Деактивирайте бюлетина за състоянието на услугата",
255 "disable_buy": "Деактивирайте действието за покупка",
256 "disable_cake_2fa": "Деактивирайте Cake 2FA",
res/values/strings_cs.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-číselný PIN",
251 "digital_and_physical_card": " digitální a fyzické předplacené debetní karty,",
252 "disable": "Zakázat",
253 + "disable_automatic_exchange_status_updates": "Zakázat aktualizace stavu automatické výměny",
254 "disable_bulletin": "Zakázat status servisního stavu",
255 "disable_buy": "Zakázat akci nákupu",
256 "disable_cake_2fa": "Zakázat Cake 2FA",
res/values/strings_de.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-stellige PIN",
251 "digital_and_physical_card": "digitale und physische Prepaid-Debitkarte",
252 "disable": "Deaktivieren",
253 + "disable_automatic_exchange_status_updates": "Deaktivieren Sie die automatischen Austauschstatusaktualisierungen",
254 "disable_bulletin": "Deaktivieren Sie das Bulletin des Service Status",
255 "disable_buy": "Kaufaktion deaktivieren",
256 "disable_cake_2fa": "Cake 2FA deaktivieren",
res/values/strings_en.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-digit PIN",
251 "digital_and_physical_card": " digital and physical prepaid debit card",
252 "disable": "Disable",
253 + "disable_automatic_exchange_status_updates": "Disable Automatic Exchange Status Updates",
254 "disable_bulletin": "Disable service status bulletin",
255 "disable_buy": "Disable buy action",
256 "disable_cake_2fa": "Disable Cake 2FA",
res/values/strings_es.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-dígito PIN",
251 "digital_and_physical_card": " tarjeta de débito prepago digital y física",
252 "disable": "Desactivar",
253 + "disable_automatic_exchange_status_updates": "Deshabilitar actualizaciones de estado de intercambio automático",
254 "disable_bulletin": "Desactivar el boletín de estado del servicio",
255 "disable_buy": "Desactivar acción de compra",
256 "disable_cake_2fa": "Desactivar 2FA",
res/values/strings_fr.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": " chiffres",
251 "digital_and_physical_card": "carte de débit prépayée numérique et physique",
252 "disable": "Désactiver",
253 + "disable_automatic_exchange_status_updates": "Désactiver les mises à jour de l'état d'échange automatique",
254 "disable_bulletin": "Désactiver le bulletin de statut de service",
255 "disable_buy": "Désactiver l'action d'achat",
256 "disable_cake_2fa": "Désactiver Cake 2FA",
res/values/strings_ha.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-lambar PIN",
251 "digital_and_physical_card": "katin zare kudi na dijital da na zahiri",
252 "disable": "Kashe",
253 + "disable_automatic_exchange_status_updates": "Musaki sabuntawar yanayin canji na atomatik",
254 "disable_bulletin": "Musaki ma'aunin sabis na sabis",
255 "disable_buy": "Kashe alama",
256 "disable_cake_2fa": "Musaki Cake 2FA",
res/values/strings_hi.arb
+2 -1
@@ -250,6 +250,7 @@
250 "digit_pin": "-अंक पिन",
251 "digital_and_physical_card": "डिजिटल और भौतिक प्रीपेड डेबिट कार्ड",
252 "disable": "अक्षम करना",
253 + "disable_automatic_exchange_status_updates": "स्वचालित एक्सचेंज स्टेटस अपडेट अक्षम करें",
254 "disable_bulletin": "सेवा स्थिति बुलेटिन अक्षम करें",
255 "disable_buy": "खरीद कार्रवाई अक्षम करें",
256 "disable_cake_2fa": "केक 2FA अक्षम करें",
@@ -568,8 +569,8 @@
569 "payjoin_unavailable_sheet_title": "Payjoin अनुपलब्ध क्यों है?",
570 "payment_id": "भुगतान ID: ",
571 "payment_made_easy": "भुगतान आसान किया गया",
571 - "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
572 "payment_was_received": "आपका भुगतान प्राप्त हुआ था।",
573 + "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
574 "payments": "भुगतान",
575 "pending": " (अपूर्ण)",
576 "percentageOf": "${amount} का",
res/values/strings_hr.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-znamenkasti PIN",
251 "digital_and_physical_card": "digitalna i fizička unaprijed plaćena debitna kartica",
252 "disable": "Onemogući",
253 + "disable_automatic_exchange_status_updates": "Onemogućite ažuriranja automatskog statusa razmjene",
254 "disable_bulletin": "Onemogućite bilten o statusu usluge",
255 "disable_buy": "Onemogući kupnju",
256 "disable_cake_2fa": "Onemogući Cake 2FA",
res/values/strings_hy.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-նիշ ՊԻՆ",
251 "digital_and_physical_card": " թվային և ֆիզիկական նախավճարային դեբետային քարտ",
252 "disable": "Անջատել",
253 + "disable_automatic_exchange_status_updates": "Անջատեք ավտոմատ փոխանակման կարգավիճակի թարմացումները",
254 "disable_bulletin": "Անջատել ծառայության վիճակի տեղեկագիրը",
255 "disable_buy": "Անջատել գնում գործողությունը",
256 "disable_cake_2fa": "Անջատել Cake 2FA",
res/values/strings_id.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-digit PIN",
251 "digital_and_physical_card": " kartu debit pra-bayar digital dan fisik",
252 "disable": "Cacat",
253 + "disable_automatic_exchange_status_updates": "Nonaktifkan Pembaruan Status Pertukaran Otomatis",
254 "disable_bulletin": "Nonaktifkan Buletin Status Layanan",
255 "disable_buy": "Nonaktifkan tindakan beli",
256 "disable_cake_2fa": "Nonaktifkan Kue 2FA",
res/values/strings_it.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-cifre PIN",
251 "digital_and_physical_card": "carta di debito prepagata digitale e fisica",
252 "disable": "Disabilita",
253 + "disable_automatic_exchange_status_updates": "Disabilita gli aggiornamenti sullo stato automatico di scambio",
254 "disable_bulletin": "Disabilita bollettino dello stato del servizio",
255 "disable_buy": "Disabilita l'azione di acquisto",
256 "disable_cake_2fa": "Disabilita Cake 2FA",
res/values/strings_ja.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "桁ピン",
251 "digital_and_physical_card": "デジタルおよび物理プリペイドデビットカード",
252 "disable": "無効にする",
253 + "disable_automatic_exchange_status_updates": "自動交換ステータスの更新を無効にします",
254 "disable_bulletin": "サービスステータス速報を無効にします",
255 "disable_buy": "購入アクションを無効にする",
256 "disable_cake_2fa": "Cake 2FA を無効にする",
res/values/strings_ko.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "자리 PIN",
251 "digital_and_physical_card": " 디지털 및 실물 선불 직불 카드",
252 "disable": "비활성화",
253 + "disable_automatic_exchange_status_updates": "자동 교환 상태 업데이트를 비활성화합니다",
254 "disable_bulletin": "서비스 상태 게시판 비활성화",
255 "disable_buy": "구매 기능 비활성화",
256 "disable_cake_2fa": "Cake 2FA 비활성화",
res/values/strings_my.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-ဂဏန်း PIN",
251 "digital_and_physical_card": " ဒစ်ဂျစ်တယ်နှင့် ရုပ်ပိုင်းဆိုင်ရာ ကြိုတင်ငွေပေးချေသော ဒက်ဘစ်ကတ်",
252 "disable": "ပိတ်ပါ။",
253 + "disable_automatic_exchange_status_updates": "အလိုအလျောက်လဲလှယ် status ကို updates များကို disable လုပ်ပါ",
254 "disable_bulletin": "ဝန်ဆောင်မှုအခြေအနေစာစောင်ကိုပိတ်ပါ",
255 "disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
256 "disable_cake_2fa": "ကိတ်မုန့် 2FA ကို ပိတ်ပါ။",
res/values/strings_nl.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-cijferige PIN",
251 "digital_and_physical_card": "digitale en fysieke prepaid debetkaart",
252 "disable": "Uitzetten",
253 + "disable_automatic_exchange_status_updates": "Schakel automatische uitwisselingsstatusupdates uit",
254 "disable_bulletin": "Schakel servicestatus Bulletin uit",
255 "disable_buy": "Koopactie uitschakelen",
256 "disable_cake_2fa": "Taart 2FA uitschakelen",
res/values/strings_pl.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-znakowy PIN",
251 "digital_and_physical_card": " cyfrowa i fizyczna przedpłacona karta debetowa",
252 "disable": "Wyłącz",
253 + "disable_automatic_exchange_status_updates": "Wyłącz automatyczne aktualizacje statusu wymiany",
254 "disable_bulletin": "Wyłącz biuletyn",
255 "disable_buy": "Wyłącz akcję kupna",
256 "disable_cake_2fa": "Wyłącz Cake 2FA",
res/values/strings_pt.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "dígitos",
251 "digital_and_physical_card": "cartão de débito pré-pago digital e físico",
252 "disable": "Desativar",
253 + "disable_automatic_exchange_status_updates": "Desativar atualizações automáticas de status de troca",
254 "disable_bulletin": "Desativar boletim de status de serviço",
255 "disable_buy": "Desativar ação de compra",
256 "disable_cake_2fa": "Desabilitar o Cake 2FA",
res/values/strings_ru.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-значный PIN",
251 "digital_and_physical_card": "цифровая и физическая предоплаченная дебетовая карта",
252 "disable": "Запрещать",
253 + "disable_automatic_exchange_status_updates": "Отключить обновления автоматического статуса обмена",
254 "disable_bulletin": "Отключить бюллетень статуса обслуживания",
255 "disable_buy": "Отключить действие покупки",
256 "disable_cake_2fa": "Отключить торт 2FA",
res/values/strings_th.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-หลัก PIN",
251 "digital_and_physical_card": "บัตรเดบิตดิจิตอลและบัตรพื้นฐาน",
252 "disable": "ปิดการใช้งาน",
253 + "disable_automatic_exchange_status_updates": "ปิดใช้งานการอัปเดตสถานะการแลกเปลี่ยนอัตโนมัติ",
254 "disable_bulletin": "ปิดการใช้งาน Bulletin สถานะบริการ",
255 "disable_buy": "ปิดการใช้งานการซื้อ",
256 "disable_cake_2fa": "ปิดการใช้งานเค้ก 2FA",
res/values/strings_tl.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-digit PIN",
251 "digital_and_physical_card": " digital at pisikal na prepaid debit card",
252 "disable": "Huwag paganahin",
253 + "disable_automatic_exchange_status_updates": "Huwag paganahin ang mga awtomatikong pag -update ng katayuan ng palitan",
254 "disable_bulletin": "Huwag paganahin ang bulletin ng katayuan ng serbisyo",
255 "disable_buy": "Huwag paganahin ang pagkilos ng pagbili",
256 "disable_cake_2fa": "Huwag paganahin ang Cake 2FA",
res/values/strings_tr.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": " haneli PIN",
251 "digital_and_physical_card": " Dijital para birimleri ile para yükleyebileceğiniz ve ek bilgiye gerek olmayan",
252 "disable": "Devre dışı bırakmak",
253 + "disable_automatic_exchange_status_updates": "Otomatik Değişim Durum Güncellemelerini Devre Dışı Bırak",
254 "disable_bulletin": "Hizmet Durumu Bültenini Devre Dışı Bırak",
255 "disable_buy": "Satın alma işlemini devre dışı bırak",
256 "disable_cake_2fa": "Cake 2FA'yı Devre Dışı Bırak",
res/values/strings_uk.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-значний PIN",
251 "digital_and_physical_card": " цифрова та фізична передплачена дебетова картка",
252 "disable": "Вимкнути",
253 + "disable_automatic_exchange_status_updates": "Вимкнути автоматичні оновлення стану обміну",
254 "disable_bulletin": "Вимкнути статус послуги",
255 "disable_buy": "Вимкнути дію покупки",
256 "disable_cake_2fa": "Вимкнути Cake 2FA",
res/values/strings_ur.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-ہندسوں کا پن",
251 "digital_and_physical_card": " ڈیجیٹل اور فزیکل پری پیڈ ڈیبٹ کارڈ",
252 "disable": "غیر فعال کریں۔",
253 + "disable_automatic_exchange_status_updates": "خودکار تبادلہ کی حیثیت کی تازہ کاریوں کو غیر فعال کریں",
254 "disable_bulletin": "خدمت کی حیثیت کا بلیٹن کو غیر فعال کریں",
255 "disable_buy": "خرید ایکشن کو غیر فعال کریں۔",
256 "disable_cake_2fa": "کیک 2FA کو غیر فعال کریں۔",
res/values/strings_vi.arb
+1
@@ -249,6 +249,7 @@
249 "digit_pin": "Mã PIN - số",
250 "digital_and_physical_card": "thẻ ghi nợ trả trước kỹ thuật số và vật lý",
251 "disable": "Vô hiệu hóa",
252 + "disable_automatic_exchange_status_updates": "Tắt các bản cập nhật trạng thái trao đổi tự động",
253 "disable_bulletin": "Vô hiệu hóa bản tin tình trạng dịch vụ",
254 "disable_buy": "Vô hiệu hóa chức năng mua",
255 "disable_cake_2fa": "Vô hiệu hóa 2FA Cake",
res/values/strings_yo.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "-díjíìtì òǹkà ìdánimọ̀ àdáni",
251 "digital_and_physical_card": " káàdì ìrajà t'ara àti ti ayélujára",
252 "disable": "Ko si",
253 + "disable_automatic_exchange_status_updates": "Mu awọn imudojuiwọn ipo paṣipaarọ aifọwọyi",
254 "disable_bulletin": "Mu blogti ipo ipo ṣiṣẹ",
255 "disable_buy": "Ko iṣọrọ ọja",
256 "disable_cake_2fa": "Ko 2FA Cake sii",
res/values/strings_zh.arb
+1
@@ -250,6 +250,7 @@
250 "digit_pin": "位 PIN",
251 "digital_and_physical_card": "数字和物理预付借记卡",
252 "disable": "停用",
253 + "disable_automatic_exchange_status_updates": "禁用自动交换状态更新",
254 "disable_bulletin": "禁用服务状态公告",
255 "disable_buy": "禁用购买操作",
256 "disable_cake_2fa": "禁用蛋糕 2FA",