What changed, and why it matters
This commit adds a new cryptocurrency price-charts feature to the Cake Wallet app. It introduces screens, data models, a price API client, local database tables to cache prices, and related UI assets. There is no direct evidence in the commit that this fixes a security vulnerability; it appears to be a normal feature addition. A few code-quality items (such as unimplemented error handling for some token types and network calls carrying an API key) are visible but do not by themselves prove an exploitable flaw.
Treat as a feature commit, not a security patch. Reviewers may want to verify that the API key is not logged, that TLS certificate pinning is applied for prices.cakewallet.com, that SQL queries are parameterized (they appear to be), and that the UnimplementedError paths for EVM/SOL tokens cannot be reached by user input. No urgent action is indicated by the diff alone.
Security signals we found
New network client sends fiatApiKey header to prices.cakewallet.com
New SQLite tables store price data and favorite assets; migration version bumped from 12 to 13
currencyFromApiString throws UnimplementedError for evm and sol token types, which could cause runtime crashes if those asset types are selected
Price parsing uses strictParsing: false and a fixed 20-digit precision, which may mask malformed API responses
ChartsBloc uses sequential/restartable transformers to mitigate concurrency issues
Evidence from the diff
The patch is a large feature commit (+2544/-270 across 94 files) implementing a Charts page. Key additions: PriceApiClient that calls prices.cakewallet.com/v3/rates with an x-api-key from secrets; PriceStore that merges cached SQLite PriceData with API responses; new ChartsAssets and PriceData SQLite tables plus a migration to version 13; BLoC/UI for chart display, asset grid, range selection, sorting, favorites, and buy/sell/swap integration. Minor changes include Money.abs(), Currency.apiString for fiat/crypto/EVM/SOL/TRX tokens, and a refactor of buy/sell page parameters. No security-relevant statements from the vendor are present in the commit message or diff.
Changed components
cw_core/lib/amount/money.dartcw_core/lib/crypto_currency.dartcw_core/lib/currency.dartcw_core/lib/db/sqlite.dartcw_core/lib/erc20_token.dartcw_core/lib/spl_token.dartcw_core/lib/tron_token.dartlib/di.dartlib/entities/default_settings_migration.dartlib/entities/fiat_currency.dartlib/new-ui/model/charts/*lib/new-ui/viewmodels/charts/*lib/new-ui/pages/charts_page.dartlib/new-ui/widgets/charts_page/*lib/view_model/buy/buy_sell_view_model.dartInspect captured patch +2544 / −270
### cw_core/lib/amount/money.dart
@@ -310,4 +310,7 @@ class Money implements Comparable<Money> {
}
return currency.symbol;
}
+
+ /// new [Money] with absolute value of this instance
+ Money abs() => copyWith(amount: amount.abs());
}
### cw_core/lib/crypto_currency.dart
@@ -44,6 +44,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
@override
String get symbol => title;
+ @override
+ String get apiString => "crypto.$title";
set enabled(bool value) => this.enabled = value;
### cw_core/lib/currency.dart
@@ -20,4 +20,5 @@ abstract class Currency {
@override
int get hashCode => decimals.hashCode ^ symbol.hashCode ^ tag.hashCode;
+ String get apiString;
}
### cw_core/lib/db/sqlite.dart
@@ -1,11 +1,11 @@
-import 'dart:io';
+import "dart:io";
-import 'package:cw_core/db/sqlite_debug.dart';
-import 'package:cw_core/root_dir.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:flutter/foundation.dart';
-import 'package:sqflite_common_ffi/sqflite_ffi.dart';
-import 'package:path/path.dart' as p;
+import "package:cw_core/db/sqlite_debug.dart";
+import "package:cw_core/root_dir.dart";
+import "package:cw_core/utils/print_verbose.dart";
+import "package:flutter/foundation.dart";
+import "package:path/path.dart" as p;
+import "package:sqflite_common_ffi/sqflite_ffi.dart";
Database? db;
@@ -16,11 +16,11 @@ Future<void> _addColumnIfNotExists(
required String definition,
}) async {
final result = await db.rawQuery("PRAGMA table_info($table)");
- final columnExists = result.any((row) => row['name'] == column);
+ final columnExists = result.any((row) => row["name"] == column);
if (!columnExists) {
await db.execute(
- 'ALTER TABLE $table ADD COLUMN $column $definition;',
+ "ALTER TABLE $table ADD COLUMN $column $definition;",
);
}
}
@@ -63,11 +63,13 @@ Future<void> _initDb({String? pathOverride}) async {
}
}
await db?.close();
- db = await openDatabase(dbFile.path, version: 12,
- onUpgrade: (Database db, int oldVersion, int newVersion) async {
- printV("migrating: $oldVersion, $newVersion");
- if (oldVersion <= 1) {
- await db.execute('''
+ db = await openDatabase(
+ dbFile.path,
+ version: 13,
+ onUpgrade: (db, oldVersion, newVersion) async {
+ printV("migrating: $oldVersion, $newVersion");
+ if (oldVersion <= 1) {
+ await db.execute("""
DELETE FROM WalletInfo
WHERE walletInfoId NOT IN (
SELECT MIN(walletInfoId)
@@ -77,10 +79,10 @@ WHERE walletInfoId NOT IN (
CREATE UNIQUE INDEX IF NOT EXISTS idx_walletinfo_id_unique
ON WalletInfo (id);
-''');
- }
- if (oldVersion <= 2) {
- await db.execute('''
+""");
+ }
+ if (oldVersion <= 2) {
+ await db.execute('''
CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings (
walletInfoId INTEGER,
accountIndex INTEGER DEFAULT -1,
@@ -91,12 +93,12 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings (
FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
);
''');
- await _addColumnIfNotExists(
- db,
- table: 'WalletInfo',
- column: 'receiveInfoboxDismissed',
- definition: 'BOOLEAN DEFAULT FALSE',
- );
+ await _addColumnIfNotExists(
+ db,
+ table: "WalletInfo",
+ column: "receiveInfoboxDismissed",
+ definition: "BOOLEAN DEFAULT FALSE",
+ );
await _addColumnIfNotExists(
db,
@@ -164,6 +166,9 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings (
definition: "BOOLEAN DEFAULT FALSE",
);
}
+ if(oldVersion <= 12) {
+ await _createChartsTables(db);
+ }
}, onCreate: (Database db, int version) async {
await db.execute('''
CREATE TABLE WalletInfo (
@@ -195,7 +200,7 @@ CREATE TABLE WalletInfo (
);
''');
- await db.execute('''
+ await db.execute("""
CREATE TABLE WalletInfoDerivationInfo (
walletInfoDerivationInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
address TEXT NOT NULL,
@@ -206,9 +211,9 @@ CREATE TABLE WalletInfoDerivationInfo (
scriptType TEXT,
description TEXT
);
-''');
+""");
- await db.execute('''
+ await db.execute('''
CREATE TABLE WalletInfoAddress (
walletInfoAddressId INTEGER PRIMARY KEY AUTOINCREMENT,
walletInfoId INTEGER,
@@ -218,7 +223,7 @@ CREATE TABLE WalletInfoAddress (
);
''');
- await db.execute('''
+ await db.execute("""
CREATE TABLE WalletInfoAddressInfo (
walletInfoAddressInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
walletInfoId INTEGER NOT NULL,
@@ -228,9 +233,9 @@ CREATE TABLE WalletInfoAddressInfo (
mapValueLabel TEXT NOT NULL,
CONSTRAINT WalletInfoAddressInfo_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
);
-''');
+""");
- await db.execute('''
+ await db.execute('''
CREATE TABLE "WalletInfoAddressMap" (
walletInfoAddressMapId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
walletInfoId INTEGER NOT NULL,
@@ -239,11 +244,11 @@ CREATE TABLE "WalletInfoAddressMap" (
CONSTRAINT WalletInfoAddress_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
);
''');
- await db.execute('''
+ await db.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_walletinfo_id_unique
ON WalletInfo (id);
-''');
- await db.execute('''
+""");
+ await db.execute('''
CREATE TABLE BalanceCardStyleSettings (
walletInfoId INTEGER,
accountIndex INTEGER DEFAULT -1,
@@ -257,18 +262,39 @@ CREATE TABLE BalanceCardStyleSettings (
FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
);
''');
- await _createBridgeTransferTable(db);
- await _createNodeTable(db);
- await _createTradeTable(db);
- await _createErc20TokenTable(db);
- await _createSplTokenTable(db);
- await _createTronTokenTable(db);
- await _createImportedNFTTable(db);
- });
+ await _createBridgeTransferTable(db);
+ await _createNodeTable(db);
+ await _createTradeTable(db);
+ await _createChartsTables(db);
+ await _createErc20TokenTable(db);
+ await _createSplTokenTable(db);
+ await _createTronTokenTable(db);
+ await _createImportedNFTTable(db);
+
+ },
+ );
+}
+
+Future<void> _createChartsTables(Database db) async {
+ await db.execute("""
+CREATE TABLE PriceData (
+ fromCurrency TEXT NOT NULL,
+ toCurrency TEXT NOT NULL,
+ timestamp INTEGER NOT NULL,
+ price TEXT NOT NULL,
+ PRIMARY KEY (fromCurrency, toCurrency, timestamp)
+);
+""");
+ await db.execute("""
+CREATE TABLE ChartsAssets (
+ asset TEXT PRIMARY KEY,
+ isFavorite BOOLEAN DEFAULT FALSE
+);
+ """);
}
Future<void> _createTradeTable(Database db) async {
- await db.execute('''
+ await db.execute("""
CREATE TABLE IF NOT EXISTS Trade (
tradeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
id TEXT NOT NULL,
@@ -323,11 +349,11 @@ CREATE TABLE IF NOT EXISTS Trade (
chainId INTEGER,
fee REAL
);
-''');
- await db.execute('''
+""");
+ await db.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_trade_id_unique
ON Trade (id);
-''');
+""");
}
Future<Map<String, dynamic>> dumpDb() async {
@@ -343,7 +369,7 @@ Future<Map<String, dynamic>> dumpDb() async {
Future<List<String>> _getTableNames(Database db) async {
final tableNames = await db.rawQuery('SELECT name FROM sqlite_master WHERE type = "table"');
- return tableNames.map((e) => (e["name"]).toString()).toList();
+ return tableNames.map((e) => e["name"].toString()).toList();
}
Future<Map<String, dynamic>> _dumpDb() async {
@@ -366,7 +392,7 @@ Future<Map<String, dynamic>> dumpCustomDb(String path) async {
}
Future<void> _createBridgeTransferTable(Database db) async {
- await db.execute('''
+ await db.execute("""
CREATE TABLE IF NOT EXISTS BridgeTransfer (
id TEXT NOT NULL PRIMARY KEY,
wallet_id TEXT NOT NULL,
@@ -385,11 +411,11 @@ CREATE TABLE IF NOT EXISTS BridgeTransfer (
error_message TEXT,
status_message TEXT
);
-''');
- await db.execute('''
+""");
+ await db.execute("""
CREATE INDEX IF NOT EXISTS idx_bridgetransfer_wallet_id
ON BridgeTransfer(wallet_id);
-''');
+""");
}
Future<void> _createErc20TokenTable(Database db) async {
@@ -478,7 +504,7 @@ ON TronToken (walletName, contractAddress);
}
Future<void> _createNodeTable(Database db) async {
- db.execute("""
+ await db.execute("""
CREATE TABLE Node (
NodeId INTEGER PRIMARY KEY,
uri TEXT NOT NULL,
### cw_core/lib/erc20_token.dart
@@ -187,4 +187,7 @@ class Erc20Token extends CryptoCurrency {
@override
int get hashCode => contractAddress.hashCode;
+
+ @override
+ String get apiString => "evm.$contractAddress";
}
### cw_core/lib/spl_token.dart
@@ -197,4 +197,7 @@ class SPLToken extends CryptoCurrency {
@override
int get hashCode => mintAddress.hashCode;
+
+ @override
+ String get apiString => "sol.$mintAddress";
}
### cw_core/lib/tron_token.dart
@@ -175,4 +175,7 @@ class TronToken extends CryptoCurrency {
@override
int get hashCode => contractAddress.hashCode;
+
+ @override
+ String get apiString => "trx.$contractAddress";
}
### lib/di.dart
@@ -52,13 +52,17 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/haven/cw_haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
+import 'package:cake_wallet/new-ui/model/charts/price_store.dart';
import 'package:cake_wallet/new-ui/new_dashboard.dart';
import 'package:cake_wallet/new-ui/pages/about_page.dart';
import 'package:cake_wallet/new-ui/pages/account_customizer.dart';
import 'package:cake_wallet/new-ui/pages/bridge/bridge_amount_page.dart';
import 'package:cake_wallet/new-ui/pages/bridge/bridge_network_page.dart';
import 'package:cake_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
import 'package:cake_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart';
+import 'package:cake_wallet/new-ui/pages/bridge/bridge_network_page.dart';
+import 'package:cake_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
+import 'package:cake_wallet/new-ui/pages/charts_page.dart';
import 'package:cake_wallet/new-ui/pages/coin_control_page.dart';
import 'package:cake_wallet/new-ui/pages/addresses_page.dart';
import 'package:cake_wallet/new-ui/pages/home_page.dart';
@@ -68,6 +72,7 @@ import 'package:cake_wallet/new-ui/pages/lightning_username_page.dart';
import 'package:cake_wallet/new-ui/pages/receive_page.dart';
import "package:cake_wallet/new-ui/pages/seed/pre_seed_page.dart";
import "package:cake_wallet/new-ui/pages/seed/show_keys_disclaimer_page.dart";
+import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart';
import 'package:cake_wallet/new-ui/viewmodels/lightning_username/lightning_username_bloc.dart';
import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart';
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
@@ -552,6 +557,15 @@ Future<void> setup({
(displayMode == BitcoinAmountDisplayMode.satoshiForLightning && lightningMode)));
});
+ getIt.registerLazySingleton<PriceStore>(() => PriceStore());
+
+ getIt.registerFactory<ChartsBloc>(
+ () => ChartsBloc(appStore: getIt.get<AppStore>(), priceStore: getIt.get<PriceStore>()));
+
+ getIt.registerFactory<ChartsPage>(() => ChartsPage(
+ chartsBloc: getIt.get<ChartsBloc>(),
+ ));
+
getIt.registerFactory<AccountCreationModal>(() => AccountCreationModal(
accountEditOrCreateViewModel: getIt.get<MoneroAccountEditOrCreateViewModel>()));
@@ -1321,11 +1335,11 @@ Future<void> setup({
getIt.registerFactory(() => BuyAmountViewModel());
- getIt.registerFactoryParam<BuySellViewModel, BuySellPageMode, void>((mode, _) => BuySellViewModel(
- mode: mode, getIt.get<AppStore>(), fiatConversionStore: getIt.get<FiatConversionStore>()));
+ getIt.registerFactoryParam<BuySellViewModel, NewBuySellParams, void>((params, _) => BuySellViewModel(
+ params: params, getIt.get<AppStore>(), fiatConversionStore: getIt.get<FiatConversionStore>()));
- getIt.registerFactoryParam<NewBuySellAmountPage, BuySellPageMode, void>((mode, _) =>
- NewBuySellAmountPage(buySellViewModel: getIt.get<BuySellViewModel>(param1: mode)));
+ getIt.registerFactoryParam<NewBuySellAmountPage, NewBuySellParams, void>((params, _) =>
+ NewBuySellAmountPage(buySellViewModel: getIt.get<BuySellViewModel>(param1: params)));
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) {
### lib/entities/default_settings_migration.dart
@@ -13,8 +13,10 @@ import 'package:cake_wallet/entities/haven_seed_store.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/monero/monero.dart';
+import 'package:cake_wallet/new-ui/model/charts/charts_asset.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:collection/collection.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/entities/sync_status_display_mode.dart';
import 'package:cw_core/node_list.dart';
@@ -648,6 +650,9 @@ Future<void> defaultSettingsMigration(
enabled: false,
);
break;
+ case 72:
+ await createDefaultChartsData();
+ break;
default:
break;
}
@@ -1338,6 +1343,12 @@ Future<void> _addXaut0TokenToExistingSolanaWallets() async {
}
}
+Future<void> createDefaultChartsData() async {
+ await ChartsAsset(asset: CryptoCurrency.btc, isFavorite: true).insert();
+ await ChartsAsset(asset: CryptoCurrency.xmr, isFavorite: false).insert();
+ await ChartsAsset(asset: CryptoCurrency.eth, isFavorite: false).insert();
+}
+
Future<void> _addTbbTokenToExistingSolanaWallets() async {
try {
final tbbToken = SPLToken(
### lib/entities/fiat_currency.dart
@@ -19,6 +19,9 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
@override
final int decimals;
+ @override
+ String get apiString => "fiat.$title";
+
@override
final String symbol;
### lib/entities/main_actions.dart
@@ -82,7 +82,7 @@ class MainActions {
canShow: (viewModel) => viewModel.hasTradeAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async {
if (!viewModel.isEnabledTradeAction) return;
- await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false);
+ await Navigator.of(context).pushNamed(Routes.buySellPage);
},
);
}
### lib/entities/new_main_actions.dart
@@ -1,14 +1,8 @@
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
-import 'package:flutter/material.dart';
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
+import "package:flutter/material.dart";
class NewMainActions {
- final Key key;
- final String Function(BuildContext context) name;
- final String image;
- final VoidCallback onTap;
- final bool Function(DashboardViewModel viewModel)? isEnabled;
- final bool Function(DashboardViewModel viewModel)? canShow;
NewMainActions._({
required this.key,
@@ -18,48 +12,54 @@ class NewMainActions {
this.isEnabled,
this.canShow,
});
+ final Key key;
+ final String Function(BuildContext context) name;
+ final String image;
+ final VoidCallback onTap;
+ final bool Function(DashboardViewModel viewModel)? isEnabled;
+ final bool Function(DashboardViewModel viewModel)? canShow;
static List<NewMainActions> all = [
homeAction,
walletsAction,
contactsAction,
appsAction,
- //chartsAction,
+ chartsAction,
];
static NewMainActions homeAction = NewMainActions._(
name: (context) => S.of(context).home,
- image: 'assets/new-ui/navbar/home.svg',
- key: ValueKey('dashboard_page_home_action_button_key'),
+ image: "assets/new-ui/navbar/home.svg",
+ key: const ValueKey("dashboard_page_home_action_button_key"),
onTap: () {},
);
static NewMainActions walletsAction = NewMainActions._(
name: (context) => S.of(context).wallets,
- image: 'assets/new-ui/navbar/wallets.svg',
- key: ValueKey('dashboard_page_wallets_action_button_key'),
+ image: "assets/new-ui/navbar/wallets.svg",
+ key: const ValueKey("dashboard_page_wallets_action_button_key"),
onTap: () {},
);
static NewMainActions contactsAction = NewMainActions._(
name: (context) => S.of(context).contacts,
- image: 'assets/new-ui/navbar/contacts.svg',
- key: ValueKey('dashboard_page_contacts_action_button_key'),
+ image: "assets/new-ui/navbar/contacts.svg",
+ key: const ValueKey("dashboard_page_contacts_action_button_key"),
onTap: () {},
);
static NewMainActions appsAction = NewMainActions._(
name: (context) => S.of(context).apps,
- image: 'assets/new-ui/navbar/apps.svg',
- key: ValueKey('dashboard_page_apps_action_button_key'),
+ image: "assets/new-ui/navbar/apps.svg",
+ key: const ValueKey("dashboard_page_apps_action_button_key"),
canShow: (dashboardVM) => dashboardVM.showApps,
onTap: () {},
);
- // static NewMainActions chartsAction = NewMainActions._(
- // name: (context) => 'Charts', //TODO S.of(context).charts,
- // image: 'assets/new-ui/navbar/charts.svg',
- // key: ValueKey('dashboard_page_charts_action_button_key'),
- // onTap: () {},
- // );
+ static NewMainActions chartsAction = NewMainActions._(
+ name: (context) => S.of(context).charts,
+ image: "assets/new-ui/navbar/charts.svg",
+ key: const ValueKey("dashboard_page_charts_action_button_key"),
+ onTap: () {},
+ );
}
### lib/main.dart
@@ -309,7 +309,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
payjoinSessionSource: payjoinSessionSource,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
- initialMigrationVersion: 71,
+ initialMigrationVersion: 72,
);
}
### lib/new-ui/long_press_popup.dart
@@ -1,58 +0,0 @@
-import 'dart:ui';
-
-import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:flutter/material.dart';
-
-class LongPressPopupBuilder extends StatelessWidget {
- const LongPressPopupBuilder(
- {super.key, required this.child, required this.popup, this.spacing = 8});
-
- final Widget child;
- final Widget popup;
- final double spacing;
-
- @override
- Widget build(BuildContext context) {
- return GestureDetector(
- onLongPress: () {
- final RenderBox renderBox = context.findRenderObject() as RenderBox;
- final offset = renderBox.localToGlobal(Offset.zero);
- final size = renderBox.size;
-
- showPopUp(
- context: context,
- builder: (context) => _buildPopup(context, offset, size),
- );
- },
- child: child,
- );
- }
-
- Widget _buildPopup(BuildContext context, Offset offset, Size size) {
- return BackdropFilter(
- filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
- child: Stack(
- children: [
- Positioned(
- left: offset.dx,
- top: offset.dy,
- width: size.width,
- height: size.height,
- child: Material(
- color: Colors.transparent,
- child: child,
- ),
- ),
- Positioned(
- left: offset.dx * 2,
- top: offset.dy + size.height + spacing,
- child: Material(
- color: Colors.transparent,
- child: popup,
- ),
- ),
- ],
- ),
- );
- }
-}
### lib/new-ui/model/charts/charts_asset.dart
@@ -0,0 +1,33 @@
+import "package:cake_wallet/new-ui/model/charts/price_data.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:cw_core/db/sqlite.dart";
+import "package:sqflite/sqflite.dart";
+
+class ChartsAsset {
+ const ChartsAsset({required this.asset, required this.isFavorite});
+
+ factory ChartsAsset.fromJson(Map<String, dynamic> json) => ChartsAsset(
+ asset: currencyFromApiString(json["asset"] as String) as CryptoCurrency,
+ isFavorite: json["isFavorite"] != 0,
+ );
+
+ final CryptoCurrency asset;
+ final bool isFavorite;
+
+ static const tableName = "ChartsAssets";
+
+ Map<String, dynamic> toJson() => {"asset": asset.apiString, "isFavorite": isFavorite ? 1 : 0};
+
+ static Future<List<ChartsAsset>> get() async {
+ final json = await db!.query(tableName);
+ return List.generate(json.length, (index) => ChartsAsset.fromJson(json[index]));
+ }
+
+ Future<void> insert() async {
+ await db!.insert(tableName, toJson(), conflictAlgorithm: ConflictAlgorithm.replace);
+ }
+
+ Future<void> remove() async {
+ await db!.delete(tableName, where: "asset = ?", whereArgs: [asset.apiString]);
+ }
+}
### lib/new-ui/model/charts/datetime_extension.dart
@@ -0,0 +1,6 @@
+extension DateTimeX on DateTime {
+ int get secondsSinceEpoch => millisecondsSinceEpoch ~/ 1000;
+
+ static DateTime fromSecondsSinceEpoch(int seconds) =>
+ DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
+}
### lib/new-ui/model/charts/price_api_client.dart
@@ -0,0 +1,78 @@
+import "dart:convert";
+
+import "package:cake_wallet/.secrets.g.dart" as secrets;
+import "package:cake_wallet/new-ui/model/charts/datetime_extension.dart";
+import "package:cake_wallet/new-ui/model/charts/price_data.dart";
+import "package:cw_core/amount/money.dart";
+import "package:cw_core/currency.dart";
+import "package:cw_core/utils/print_verbose.dart";
+import "package:cw_core/utils/proxy_wrapper.dart";
+
+const priceApiHost = "prices.cakewallet.com";
+
+class PriceRequest {
+ const PriceRequest({
+ required this.interval,
+ required this.from,
+ required this.to,
+ this.beginTime,
+ this.count,
+ });
+
+ final DateTime? beginTime;
+ final Duration interval;
+ final int? count;
+ final Currency from;
+ final Currency to;
+
+ Uri get uri => Uri.https(priceApiHost, "/v3/rates", {
+ "start": (beginTime?.secondsSinceEpoch ?? 0).toString(),
+ "end": DateTime.now().secondsSinceEpoch.toString(),
+ "interval": "${interval.inSeconds}",
+ if (count != null) "count": count.toString(),
+ "quote": from.apiString,
+ "base": to.apiString,
+ });
+}
+
+class PriceApiClient {
+ static Future<Map<String, dynamic>?> _getJson(Uri uri) async {
+ final resp =
+ await ProxyWrapper().get(headers: {"x-api-key": secrets.fiatApiKey}, clearnetUri: uri);
+ if (!(resp.statusCode >= 200 && resp.statusCode < 300)) {
+ printV("server returned code: ${resp.statusCode}\nuri: ${uri}\nresp body: ${resp.body}");
+ return null;
+ }
+ try {
+ return jsonDecode(resp.body) as Map<String, dynamic>?;
+ } catch (e) {
+ printV("failed to decode response for ${uri.host}/${uri.path}: $e");
+ return null;
+ }
+ }
+
+ static Future<List<PriceData>> getPrices(PriceRequest request) async {
+ final List<PriceData> ret = [];
+ printV(request.uri);
+ final data = await _getJson(request.uri);
+ if (data == null) {
+ return [];
+ }
+ final results = data["results"] as Map<String, dynamic>?;
+ if (results == null) {
+ printV(data.toString());
+ return [];
+ }
+ printV(data.toString());
+ for (final time in results.keys) {
+ ret.add(
+ PriceData(
+ time: DateTimeX.fromSecondsSinceEpoch(int.parse(time)),
+ base: request.from,
+ quote: Money.parse((results[time] as num).toStringAsFixed(20), request.to, strictParsing: false),
+ ),
+ );
+ }
+ return ret;
+ }
+}
### lib/new-ui/model/charts/price_data.dart
@@ -0,0 +1,104 @@
+import "package:cake_wallet/entities/fiat_currency.dart";
+import "package:cake_wallet/new-ui/model/charts/datetime_extension.dart";
+import "package:cw_core/amount/money.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:cw_core/currency.dart";
+import "package:cw_core/db/sqlite.dart";
+import "package:sqflite/sqflite.dart";
+
+Currency currencyFromApiString(String key) {
+ final parts = key.split(".");
+ final type = parts[0];
+ final id = parts[1];
+
+ switch (type) {
+ case "fiat":
+ return FiatCurrency.deserialize(raw: id);
+ case "crypto":
+ return CryptoCurrency.fromString(id);
+ case "evm":
+ throw UnimplementedError();
+ case "sol":
+ throw UnimplementedError();
+ }
+ throw Exception("unknown api string");
+}
+
+class PriceData implements Comparable<PriceData> {
+ const PriceData({required this.time, required this.base, required this.quote,});
+
+ factory PriceData.fromJson(Map<String, dynamic> json) => PriceData(
+ time: DateTimeX.fromSecondsSinceEpoch(json["timestamp"] as int),
+ base: currencyFromApiString(json["fromCurrency"] as String),
+ quote: Money.parse(
+ json["price"] as String, currencyFromApiString(json["toCurrency"] as String),
+ strictParsing: false),
+ );
+ final DateTime time;
+ final Currency base;
+ final Money quote;
+
+ static const tableName = "PriceData";
+
+ Map<String, dynamic> toJson() => {
+ "timestamp": time.secondsSinceEpoch.toString(),
+ "price": quote.toString(),
+ "fromCurrency": base.apiString,
+ "toCurrency": quote.currency.apiString,
+ };
+
+ static Future<List<PriceData>> get(
+ Currency from,
+ Currency to,
+ DateTime? start,
+ DateTime? end,
+ ) async {
+ final json = await db!.query(
+ tableName,
+ where: "fromCurrency = ? AND toCurrency = ? AND timestamp >= ? AND timestamp <= ?",
+ whereArgs: [
+ from.apiString,
+ to.apiString,
+ start?.secondsSinceEpoch ?? 0,
+ // dart doesn't have INT_MAX, apparently.
+ end?.secondsSinceEpoch ?? 0x7FFFFFFFFFFFFFFF,
+ ],
+ );
+ return List.generate(json.length, (index) => PriceData.fromJson(json[index]));
+ }
+
+ Future<void> insert() async {
+ await db!.insert(tableName, toJson(), conflictAlgorithm: ConflictAlgorithm.replace);
+ }
+
+ static Future<void> insertMany(Iterable<PriceData> data) async {
+ if (data.isEmpty) {
+ return;
+ }
+
+ final batch = db!.batch();
+
+ for (final datum in data) {
+ batch.insert(
+ tableName,
+ datum.toJson(),
+ conflictAlgorithm: ConflictAlgorithm.replace,
+ );
+ }
+
+ await batch.commit(noResult: true);
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is PriceData &&
+ time == other.time &&
+ base == other.base &&
+ quote.currency == other.quote.currency;
+
+ @override
+ int get hashCode => Object.hash(time, base, quote.currency);
+
+ @override
+ int compareTo(PriceData other) => time.compareTo(other.time);
+}
### lib/new-ui/model/charts/price_store.dart
@@ -0,0 +1,133 @@
+import "package:cake_wallet/new-ui/model/charts/price_api_client.dart";
+import "package:cake_wallet/new-ui/model/charts/price_data.dart";
+import "package:cake_wallet/new-ui/model/charts/util/chart_range.dart";
+import "package:cw_core/currency.dart";
+import "package:cw_core/utils/print_verbose.dart";
+
+abstract class PriceSource {
+ const PriceSource();
+
+ Future<List<PriceData>> get(
+ DateTime start,
+ DateTime end,
+ Currency from,
+ Currency to,
+ Duration interval,
+ );
+}
+
+mixin UpdatablePriceSource {
+ Future<void> update(Iterable<PriceData> newData);
+}
+
+class DatabasePriceSource extends PriceSource with UpdatablePriceSource {
+ const DatabasePriceSource();
+
+ @override
+ Future<List<PriceData>> get(
+ DateTime start,
+ DateTime end,
+ Currency from,
+ Currency to,
+ Duration interval,
+ ) =>
+ PriceData.get(from, to, start, end);
+
+ @override
+ Future<void> update(Iterable<PriceData> newData) async {
+ await PriceData.insertMany(newData);
+ }
+}
+
+class ApiPriceSource extends PriceSource {
+ const ApiPriceSource();
+
+ @override
+ Future<List<PriceData>> get(
+ DateTime start,
+ DateTime end,
+ Currency from,
+ Currency to,
+ Duration interval,
+ ) =>
+ PriceApiClient.getPrices(
+ PriceRequest(beginTime: start, interval: interval, from: from, to: to),
+ );
+}
+
+class PriceStore {
+ static const priceSources = [
+ // TODO InMemoryPriceSource() - easily implementable w this pattern but idk if we need to optimize this that much
+ DatabasePriceSource(),
+ ApiPriceSource(),
+ ];
+
+ Future<List<PriceData>> getPrices(Currency from, Currency to, ChartRange range) async {
+ final Set<PriceData> data = {};
+
+ final end = DateTime.now();
+ DateTime? start;
+ if (range.duration == null) {
+ start = DateTime.fromMillisecondsSinceEpoch(0);
+ } else {
+ start = end.subtract(range.duration!);
+ }
+ start = _alignedStart(start, range.dataPrecision);
+ final alignedStart = start;
+
+ for (final source in priceSources) {
+ final sourceData = await source.get(start!, end, from, to, range.dataPrecision);
+ data.addAll(sourceData);
+ start = _firstUnavailablePrice(data.toList(), range.dataPrecision, start, end);
+ if (start == null) {
+ break;
+ }
+ }
+
+ for (final source in priceSources) {
+ if (source case final UpdatablePriceSource s) {
+ await s.update(data);
+ }
+ }
+
+ return _alignedData(alignedStart, end, data, range.dataPrecision);
+ }
+
+ static List<PriceData> _alignedData(
+ DateTime start,
+ DateTime end,
+ Iterable<PriceData> data,
+ Duration precision,
+ ) {
+ final ret = data
+ .where((datum) => datum.time.millisecondsSinceEpoch % precision.inMilliseconds == 0)
+ .toList();
+ ret.sort((a, b) => a.time.compareTo(b.time));
+ return ret;
+ }
+
+ static DateTime _alignedStart(DateTime start, Duration precision) {
+ final alignedStartMs =
+ (start.millisecondsSinceEpoch ~/ precision.inMilliseconds) * precision.inMilliseconds;
+ return DateTime.fromMillisecondsSinceEpoch(alignedStartMs);
+ }
+
+ static DateTime? _firstUnavailablePrice(
+ List<PriceData> prices,
+ Duration precision,
+ DateTime start,
+ DateTime end,
+ ) {
+ if (prices.isEmpty) {
+ return start;
+ }
+ prices.sort();
+ final last = prices.last;
+ printV("last.time: ${last.time.toIso8601String()} end: ${end.toIso8601String()}");
+ if (last.time.isAfter(end.subtract(precision)) ||
+ last.time.isAtSameMomentAs(end.subtract(precision))) {
+ return null;
+ }
+ return last.time.add(precision);
+ }
+}
### lib/new-ui/model/charts/util/chart_range.dart
@@ -0,0 +1,21 @@
+class ChartRange {
+ const ChartRange._(this.duration, this.displayText, this.dataPrecision);
+
+ final Duration? duration;
+ final String displayText;
+
+ // api can handle any precision up to 5min, regardless of data range.
+ // we set a "preferred" precision for 2 reasons:
+ // - less precision means less data sent, less bandwidth used, and thus less load on the backend
+ // - less data points means the chart is easier to look through on a small screen
+ final Duration dataPrecision;
+
+ static const oneHour = ChartRange._(Duration(hours: 1), "1H", Duration(minutes: 5));
+ static const oneDay = ChartRange._(Duration(days: 1), "1D", Duration(minutes: 15));
+ static const sevenDays = ChartRange._(Duration(days: 7), "7D", Duration(hours: 1));
+ static const thirtyDays = ChartRange._(Duration(days: 30), "30D", Duration(hours: 4));
+ static const oneYear = ChartRange._(Duration(days: 365), "1Y", Duration(days: 1));
+ static const all = ChartRange._(null, "ALL", Duration(days: 1));
+
+ static const ranges = [oneHour, oneDay, sevenDays, thirtyDays, oneYear, all];
+}
### lib/new-ui/model/charts/util/price_change_data.dart
@@ -0,0 +1,19 @@
+import "package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart";
+import "package:cw_core/amount/money.dart";
+
+class PriceChangeData implements Comparable<PriceChangeData> {
+ const PriceChangeData({required this.direction, required this.amount, required this.percentage});
+
+ final PriceChangeDirection direction;
+ final Money amount;
+ final String percentage;
+
+ @override
+ int compareTo(PriceChangeData other) {
+ final double thisValue =
+ double.parse(percentage) * (direction == PriceChangeDirection.up ? 1 : -1);
+ final double otherValue =
+ double.parse(other.percentage) * (other.direction == PriceChangeDirection.up ? 1 : -1);
+ return thisValue.compareTo(otherValue);
+ }
+}
### lib/new-ui/model/charts/util/price_change_direction.dart
@@ -0,0 +1,11 @@
+import "dart:ui";
+
+class PriceChangeDirection {
+
+ const PriceChangeDirection._(this.color, this.symbol);
+ final Color color;
+ final String symbol;
+
+ static const up = PriceChangeDirection._(Color(0xFF6FC84E), "+");
+ static const down = PriceChangeDirection._(Color(0xFFEA696F), "-");
+}
### lib/new-ui/model/charts/util/price_data_sort_criteria.dart
@@ -0,0 +1,141 @@
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_change_data.dart";
+import "package:cw_core/crypto_currency.dart";
+
+const List<CryptoCurrency> cryptoCurrenciesByMarketcap = [
+ CryptoCurrency.btc,
+ CryptoCurrency.eth,
+ CryptoCurrency.usdt,
+ CryptoCurrency.xrp,
+ CryptoCurrency.bnb,
+ CryptoCurrency.sol,
+ CryptoCurrency.usdc,
+ CryptoCurrency.doge,
+ CryptoCurrency.trx,
+ CryptoCurrency.ton,
+ CryptoCurrency.bch,
+ CryptoCurrency.ltc,
+ CryptoCurrency.xmr,
+ CryptoCurrency.matic,
+ CryptoCurrency.arb,
+ CryptoCurrency.dai,
+ CryptoCurrency.paxg,
+ CryptoCurrency.zec,
+ CryptoCurrency.dcr,
+ CryptoCurrency.nano,
+ CryptoCurrency.zano,
+ CryptoCurrency.deuro,
+ CryptoCurrency.wow,
+];
+
+abstract class PriceDataSortCriterium {
+ const PriceDataSortCriterium();
+
+ String get name;
+
+ String get iconPath;
+
+ int comparator(
+ PriceChangeData changeDataA,
+ PriceChangeData changeDataB,
+ CryptoCurrency a,
+ CryptoCurrency b,
+ );
+
+ static const all = [
+ MarketcapSortCriterium(),
+ GainsSortCriterium(),
+ LossesSortCriterium(),
+ AlphabeticalSortCriterium(),
+ ];
+}
+
+class AlphabeticalSortCriterium extends PriceDataSortCriterium {
+ const AlphabeticalSortCriterium();
+
+ @override
+ String get name => S.current.alphabetical;
+
+ @override
+ String get iconPath => "assets/new-ui/charts_sort_criteria/alpha.svg";
+
+ @override
+ int comparator(
+ PriceChangeData changeDataA,
+ PriceChangeData changeDataB,
+ CryptoCurrency a,
+ CryptoCurrency b,
+ ) =>
+ (a.fullName ?? a.title).compareTo(b.fullName ?? b.title);
+}
+
+class MarketcapSortCriterium extends PriceDataSortCriterium {
+ const MarketcapSortCriterium();
+
+ @override
+ String get name => S.current.marketcap;
+
+ @override
+ String get iconPath => "assets/new-ui/charts_sort_criteria/marketcap.svg";
+
+ @override
+ int comparator(
+ PriceChangeData changeDataA,
+ PriceChangeData changeDataB,
+ CryptoCurrency a,
+ CryptoCurrency b,
+ ) {
+ final aIndex = cryptoCurrenciesByMarketcap.indexOf(a);
+ final bIndex = cryptoCurrenciesByMarketcap.indexOf(b);
+
+ if (aIndex != -1 && bIndex != -1) {
+ return aIndex.compareTo(bIndex);
+ }
+ if (aIndex != -1) {
+ return -1;
+ }
+ if (bIndex != -1) {
+ return 1;
+ }
+
+ return (a.fullName ?? a.title).compareTo(b.fullName ?? b.title);
+ }
+}
+
+class GainsSortCriterium extends PriceDataSortCriterium {
+ const GainsSortCriterium();
+
+ @override
+ String get name => S.current.gains;
+
+ @override
+ String get iconPath => "assets/new-ui/charts_sort_criteria/gains.svg";
+
+ @override
+ int comparator(
+ PriceChangeData changeDataA,
+ PriceChangeData changeDataB,
+ CryptoCurrency a,
+ CryptoCurrency b,
+ ) =>
+ changeDataB.compareTo(changeDataA);
+}
+
+class LossesSortCriterium extends PriceDataSortCriterium {
+ const LossesSortCriterium();
+
+ @override
+ String get name => S.current.losses;
+
+ @override
+ String get iconPath => "assets/new-ui/charts_sort_criteria/losses.svg";
+
+ @override
+ int comparator(
+ PriceChangeData changeDataA,
+ PriceChangeData changeDataB,
+ CryptoCurrency a,
+ CryptoCurrency b,
+ ) =>
+ changeDataA.compareTo(changeDataB);
+}
### lib/new-ui/new_dashboard.dart
@@ -3,6 +3,8 @@ import 'dart:io';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import "package:cake_wallet/new-ui/page_open_listener.dart";
+import "package:cake_wallet/new-ui/page_open_listener.dart";
+import 'package:cake_wallet/new-ui/pages/charts_page.dart';
import 'package:cake_wallet/new-ui/pages/home_page.dart';
import 'package:cake_wallet/new-ui/widgets/changelog_modal.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
@@ -36,7 +38,7 @@ class NewDashboard extends StatefulWidget {
getIt.get<WalletListPage>(),
getIt.get<ContactListPage>(),
getIt.get<CakeFeaturesPage>(),
- Placeholder(),
+ getIt.get<ChartsPage>()
];
@override
### lib/new-ui/page_open_listener.dart
@@ -1,3 +1,5 @@
+
+
abstract interface class PageOpenListener {
void onPageOpen();
}
### lib/new-ui/pages/addresses_page.dart
@@ -3,11 +3,11 @@ import 'dart:ui';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
-import 'package:cake_wallet/new-ui/long_press_popup.dart';
+import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_popup.dart';
import 'package:cake_wallet/new-ui/widgets/addresses_page/address_info.dart';
import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
-import 'package:cake_wallet/new-ui/widgets/long_press_menu.dart';
+import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_menu.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
### lib/new-ui/pages/buy_sell/buy_sell_amount_page.dart
@@ -27,6 +27,13 @@ import "package:flutter/material.dart";
import "package:flutter_mobx/flutter_mobx.dart";
import "package:mobx/mobx.dart";
+class NewBuySellParams {
+ NewBuySellParams({required this.mode, this.initialCurrency});
+
+ final BuySellPageMode mode;
+ final CryptoCurrency? initialCurrency;
+}
+
class NewBuySellAmountPage extends StatefulWidget {
const NewBuySellAmountPage({required this.buySellViewModel, super.key});
### lib/new-ui/pages/charts_page.dart
@@ -0,0 +1,103 @@
+import "package:cake_wallet/new-ui/page_open_listener.dart";
+import "package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/asset_grid.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/asset_grid_header.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/chart_header.dart";
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:flutter/cupertino.dart";
+import "package:flutter/foundation.dart";
+import "package:flutter/material.dart";
+import "package:flutter_bloc/flutter_bloc.dart";
+
+class ChartsPage extends StatelessWidget implements PageOpenListener {
+ const ChartsPage({required this.chartsBloc, super.key});
+
+ final ChartsBloc chartsBloc;
+
+ @override
+ void onPageOpen() {
+ chartsBloc.add(Init());
+ }
+
+ @override
+ Widget build(BuildContext context) => BlocProvider(
+ create: (context) => chartsBloc,
+ child: BlocBuilder<ChartsBloc, ChartsState>(
+ builder: (context, state) {
+ if (state is ChartsInitial) {
+ return const Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 24,
+ children: [
+ CakeImageWidget(
+ width: 36,
+ height: 36,
+ imageUrl: "assets/new-ui/navbar/charts.svg",
+ ),
+ CupertinoActivityIndicator(),
+ if (kDebugMode)
+ Text(
+ "devs: if this doesn't go away in a few seconds, either your device is painfully slow or the db is messed up",
+ ),
+ ],
+ ),
+ );
+ }
+
+ return Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ colors: [
+ Theme.of(context).colorScheme.surface,
+ Theme.of(context).colorScheme.surfaceDim,
+ ],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ child: CustomScrollView(
+ physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
+ slivers: [
+ SliverPadding(
+ padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
+ sliver: CupertinoSliverRefreshControl(
+ refreshTriggerPullDistance: 160,
+ refreshIndicatorExtent: 90,
+ onRefresh: () async => chartsBloc.add(PageRefreshed()),
+ ),
+ ),
+ SliverToBoxAdapter(
+ child: SafeArea(
+ child: Column(
+ spacing: 24,
+ children: [
+ if (state is ChartsStateWithData)
+ ChartHeader(
+ currency: state.pinnedCurrency,
+ chartHeight: 100,
+ chartPadding: 22,
+ centered: false,
+ favorite: true,
+ ),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 18),
+ child: Column(
+ spacing: 24,
+ children: [ChartsAssetGridHeader(), ChartsAssetGrid()],
+ ),
+ ),
+ const SizedBox(
+ height: 96,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+}
### lib/new-ui/viewmodels/charts/charts_bloc.dart
@@ -0,0 +1,173 @@
+import "dart:async";
+
+import "package:bloc/bloc.dart";
+import "package:bloc_concurrency/bloc_concurrency.dart";
+import "package:cake_wallet/core/utilities.dart";
+import "package:cake_wallet/entities/fiat_currency.dart";
+import "package:cake_wallet/new-ui/model/charts/charts_asset.dart";
+import "package:cake_wallet/new-ui/model/charts/price_data.dart";
+import "package:cake_wallet/new-ui/model/charts/price_store.dart";
+import "package:cake_wallet/new-ui/model/charts/util/chart_range.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_change_data.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_data_sort_criteria.dart";
+import "package:cake_wallet/store/app_store.dart";
+import "package:cw_core/amount/money.dart";
+import "package:cw_core/crypto_amount_format.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:flutter/foundation.dart";
+
+part "charts_event.dart";
+
+part "charts_state.dart";
+
+class ChartsBloc extends Bloc<ChartsEvent, ChartsState> {
+ ChartsBloc({required this.priceStore, required this.appStore}) : super(const ChartsInitial()) {
+ on<RangeChanged>(_onRangeChanged, transformer: sequential());
+ on<SortingCriteriumChanged>(_onSortingCriteriumChanged);
+ on<CurrencyAdded>(_onCurrencyAdded, transformer: sequential());
+ on<CurrencyRemoved>(_onCurrencyRemoved, transformer: sequential());
+ on<CurrencyPinned>(_onCurrencyPinned, transformer: sequential());
+ on<PageRefreshed>(_onPageRefreshed, transformer: sequential());
+ on<PageLoadStarted>(_onPageLoadStarted, transformer: restartable());
+ on<Init>(_init);
+ }
+
+ final PriceStore priceStore;
+ final AppStore appStore;
+
+ Future<void> _init(Init event, Emitter<ChartsState> emit) async {
+ if (state is! ChartsInitial) {
+ return;
+ }
+
+ final assets = await ChartsAsset.get();
+
+ if (assets.isEmpty) {
+ // generally this shouldn't happen, but i wanna make sure we can recover from a broken db
+ assets.add(const ChartsAsset(asset: CryptoCurrency.btc, isFavorite: true));
+ unawaited(assets.first.insert());
+ }
+
+ if (assets.firstWhereOrNull((item) => item.isFavorite) == null) {
+ assets.first = ChartsAsset(asset: assets.first.asset, isFavorite: true);
+ unawaited(assets.first.insert());
+ }
+
+ emit(
+ ChartsLoading(
+ pinnedCurrency: assets.firstWhere((item) => item.isFavorite).asset,
+ currencies: assets.map((item) => item.asset).toList(),
+ range: ChartRange.all,
+ sortCriterium: PriceDataSortCriterium.all.first,
+ ),
+ );
+ add(PageLoadStarted());
+ }
+
+ Future<void> _onPageLoadStarted(PageLoadStarted event, Emitter<ChartsState> emit) async {
+ if (state case final ChartsStateWithData s) {
+ final Map<CryptoCurrency, List<PriceData>> data = {};
+ final currencies = s.currencies.toList();
+ final range = s.range;
+ for (final curr in currencies) {
+ data[curr] = await priceStore.getPrices(appStore.settingsStore.fiatCurrency, curr, range);
+ }
+ emit(
+ ChartsLoaded(
+ pinnedCurrency: s.pinnedCurrency,
+ prices: data,
+ range: s.range,
+ sortCriterium: s.sortCriterium,
+ ),
+ );
+ } else {
+ throw Exception("attempted price load without currency data");
+ }
+ }
+
+ Future<void> _onRangeChanged(
+ RangeChanged event,
+ Emitter<ChartsState> emit,
+ ) async {
+ if (state case final ChartsStateWithData s) {
+ emit(s.toLoading().copyWith(range: event.newRange));
+ add(PageLoadStarted());
+ }
+ }
+
+ Future<void> _onSortingCriteriumChanged(
+ SortingCriteriumChanged event,
+ Emitter<ChartsState> emit,
+ ) async {
+ if (state case final ChartsStateWithData s) {
+ emit(s.copyWith(sortCriterium: event.newCriterium));
+ }
+ }
+
+ Future<void> _onCurrencyAdded(
+ CurrencyAdded event,
+ Emitter<ChartsState> emit,
+ ) async {
+ if (state case final ChartsStateWithData s) {
+ final newCurrencies = s.currencies..add(event.currency);
+ await ChartsAsset(asset: event.currency, isFavorite: false).insert();
+ emit(s.toLoading().copyWith(currencies: newCurrencies));
+ add(PageLoadStarted());
+ }
+ }
+
+ Future<void> _onCurrencyRemoved(
+ CurrencyRemoved event,
+ Emitter<ChartsState> emit,
+ ) async {
+ if (state case final ChartsStateWithData s) {
+ final newCurrencies = s.currencies..remove(event.currency);
+
+ if (newCurrencies.isEmpty) {
+ throw Exception(
+ "removed the last currency ${kDebugMode ? "- your ui should block this! what did you do?" : ""}",
+ );
+ }
+
+ final CryptoCurrency newPin;
+ if (s.pinnedCurrency == event.currency) {
+ newPin = newCurrencies.first;
+ await ChartsAsset(asset: s.pinnedCurrency, isFavorite: false).insert();
+ await ChartsAsset(asset: newPin, isFavorite: true).insert();
+ } else {
+ newPin = s.pinnedCurrency;
+ }
+
+ await ChartsAsset(asset: event.currency, isFavorite: false).remove();
+
+ emit(s.toLoading().copyWith(currencies: newCurrencies, pinnedCurrency: newPin));
+ add(PageLoadStarted());
+ }
+ }
+
+ Future<void> _onCurrencyPinned(
+ CurrencyPinned event,
+ Emitter<ChartsState> emit,
+ ) async {
+ if (state case final ChartsStateWithData s) {
+ if (s.pinnedCurrency == event.currency) {
+ return;
+ }
+ await ChartsAsset(asset: s.pinnedCurrency, isFavorite: false).insert();
+ await ChartsAsset(asset: event.currency, isFavorite: true).insert();
+ emit(s.toLoading().copyWith(pinnedCurrency: event.currency));
+ add(PageLoadStarted());
+ }
+ }
+
+ Future<void> _onPageRefreshed(
+ PageRefreshed event,
+ Emitter<ChartsState> emit,
+ ) async {
+ if (state case final ChartsStateWithData s) {
+ emit(s.toLoading());
+ add(PageLoadStarted());
+ }
+ }
+}
### lib/new-ui/viewmodels/charts/charts_event.dart
@@ -0,0 +1,42 @@
+part of 'charts_bloc.dart';
+
+@immutable
+sealed class ChartsEvent {
+ const ChartsEvent();
+}
+
+class RangeChanged extends ChartsEvent {
+ const RangeChanged({required this.newRange});
+
+ final ChartRange newRange;
+}
+
+class SortingCriteriumChanged extends ChartsEvent {
+ const SortingCriteriumChanged({required this.newCriterium});
+
+ final PriceDataSortCriterium newCriterium;
+}
+
+class CurrencyAdded extends ChartsEvent {
+ const CurrencyAdded({required this.currency});
+
+ final CryptoCurrency currency;
+}
+
+class CurrencyRemoved extends ChartsEvent {
+ const CurrencyRemoved({required this.currency});
+
+ final CryptoCurrency currency;
+}
+
+class CurrencyPinned extends ChartsEvent {
+ const CurrencyPinned({required this.currency});
+
+ final CryptoCurrency currency;
+}
+
+class PageRefreshed extends ChartsEvent {}
+
+class PageLoadStarted extends ChartsEvent {}
+
+class Init extends ChartsEvent {}
### lib/new-ui/viewmodels/charts/charts_state.dart
@@ -0,0 +1,132 @@
+part of "charts_bloc.dart";
+
+@immutable
+sealed class ChartsState {
+ const ChartsState();
+}
+
+final class ChartsInitial extends ChartsState {
+ const ChartsInitial();
+}
+
+abstract final class ChartsStateWithData extends ChartsState {
+ const ChartsStateWithData({
+ required this.pinnedCurrency,
+ required this.range,
+ required this.sortCriterium,
+ });
+
+ final CryptoCurrency pinnedCurrency;
+
+ List<CryptoCurrency> get currencies;
+
+ final PriceDataSortCriterium sortCriterium;
+ final ChartRange range;
+
+ Money? priceFor(CryptoCurrency curr) => null;
+
+ String get fiatTicker => "";
+
+ bool get hasSingleCurrency => currencies.length == 1;
+
+ ChartsStateWithData copyWith({
+ CryptoCurrency? pinnedCurrency,
+ ChartRange? range,
+ PriceDataSortCriterium? sortCriterium,
+ });
+
+ ChartsLoading toLoading() => ChartsLoading(
+ pinnedCurrency: pinnedCurrency,
+ currencies: currencies,
+ range: range,
+ sortCriterium: sortCriterium,
+ );
+}
+
+final class ChartsLoading extends ChartsStateWithData {
+ const ChartsLoading({
+ required super.pinnedCurrency,
+ required this.currencies,
+ required super.range,
+ required super.sortCriterium,
+ });
+
+ @override
+ final List<CryptoCurrency> currencies;
+
+ @override
+ ChartsLoading copyWith({
+ CryptoCurrency? pinnedCurrency,
+ List<CryptoCurrency>? currencies,
+ ChartRange? range,
+ PriceDataSortCriterium? sortCriterium,
+ }) =>
+ ChartsLoading(
+ pinnedCurrency: pinnedCurrency ?? this.pinnedCurrency,
+ currencies: currencies ?? this.currencies,
+ range: range ?? this.range,
+ sortCriterium: sortCriterium ?? this.sortCriterium,
+ );
+}
+
+final class ChartsLoaded extends ChartsStateWithData {
+ const ChartsLoaded({
+ required super.pinnedCurrency,
+ required Map<CryptoCurrency, List<PriceData>> prices,
+ required super.range,
+ required super.sortCriterium,
+ }) : _prices = prices;
+ final Map<CryptoCurrency, List<PriceData>> _prices;
+
+ @override
+ String get fiatTicker => _prices[_prices.keys.first]?.firstOrNull?.base.name ?? "";
+
+ @override
+ List<CryptoCurrency> get currencies {
+ final list = _prices.keys.toList();
+ list.sort((a, b) => sortCriterium.comparator(changeDataFor(a), changeDataFor(b), a, b));
+ return list;
+ }
+
+ @override
+ Money? priceFor(CryptoCurrency curr) => _prices[curr]?.lastOrNull?.quote;
+
+ PriceChangeData changeDataFor(CryptoCurrency curr) {
+ final latestPrice = _prices[curr]?.lastOrNull?.quote ?? Money.zero(FiatCurrency.usd);
+ final secondLatestPrice = _prices[curr]?.firstOrNull?.quote ?? Money.zero(FiatCurrency.usd);
+
+ final direction =
+ latestPrice >= secondLatestPrice ? PriceChangeDirection.up : PriceChangeDirection.down;
+ final double percentage;
+ if(secondLatestPrice.isZero) {
+ percentage = 0;
+ } else {
+ percentage =
+ (((double.parse(latestPrice.toString()) - double.parse(secondLatestPrice.toString())) /
+ double.parse(secondLatestPrice.toString())) * 100).abs();
+ }
+ final amount = (latestPrice - secondLatestPrice).abs();
+
+ return PriceChangeData(
+ direction: direction,
+ amount: amount,
+ percentage: percentage.toStringAsFixed(2),
+ );
+ }
+
+ List<PriceData> dataFor(CryptoCurrency curr) => _prices[curr] ?? [];
+
+ @override
+ ChartsLoaded copyWith({
+ CryptoCurrency? pinnedCurrency,
+ Map<CryptoCurrency, List<PriceData>>? prices,
+ ChartRange? range,
+ PriceDataSortCriterium? sortCriterium,
+ }) =>
+ ChartsLoaded(
+ pinnedCurrency: pinnedCurrency ?? this.pinnedCurrency,
+ prices: prices ?? _prices,
+ range: range ?? this.range,
+ sortCriterium: sortCriterium ?? this.sortCriterium,
+ );
+}
### lib/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart
@@ -3,6 +3,7 @@ import "package:cake_wallet/generated/i18n.dart";
import "package:cake_wallet/main.dart";
import "package:cake_wallet/new-ui/modal_navigator.dart";
import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart";
+import "package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart";
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
import "package:cake_wallet/themes/core/theme_extension.dart";
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
@@ -68,7 +69,7 @@ class BuySellSelectorModal extends StatelessWidget {
void openBuySellPage(BuildContext context, BuySellPageMode mode) {
Navigator.of(context).pop();
- final page = getIt.get<NewBuySellAmountPage>(param1: mode);
+ final page = getIt.get<NewBuySellAmountPage>(param1: NewBuySellParams(mode: mode));
showModalBottomSheet(
useSafeArea: true,
isScrollControlled: true,
### lib/new-ui/widgets/charts_page/asset_grid.dart
@@ -0,0 +1,222 @@
+import "dart:async";
+
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_change_data.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart";
+import "package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/chart_modal.dart";
+import "package:cake_wallet/new-ui/widgets/long_press_menu/long_press_footer.dart";
+import "package:cake_wallet/new-ui/widgets/long_press_menu/long_press_menu.dart";
+import "package:cake_wallet/new-ui/widgets/long_press_menu/long_press_popup.dart";
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:cake_wallet/themes/core/theme_extension.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:flutter/material.dart";
+import "package:flutter/services.dart";
+import "package:flutter_bloc/flutter_bloc.dart";
+
+class ChartsAssetGrid extends StatelessWidget {
+ const ChartsAssetGrid({super.key});
+
+ @override
+ Widget build(BuildContext context) => BlocBuilder<ChartsBloc, ChartsState>(
+ builder: (context, state) {
+ if (state is ChartsStateWithData) {
+ final currencies = state.currencies;
+ return GridView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: currencies.length,
+ gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 2,
+ crossAxisSpacing: 10,
+ mainAxisSpacing: 10,
+ mainAxisExtent: 105,),
+ itemBuilder: (context, index) {
+ final curr = currencies[index];
+ return ChartsAssetCard(
+ currency: curr,
+ price: state.priceFor(curr)?.toStringWithPrecision(fractionalDigits: 2) ?? "...",
+ ticker: state.fiatTicker,
+ changeData: state is ChartsLoaded ? state.changeDataFor(curr) : null,
+ favorite: curr == state.pinnedCurrency,
+ isSingleCurrency: state.hasSingleCurrency,
+ );
+ },);
+ } else {
+ return const SizedBox.shrink();
+ }
+ },
+ );
+}
+
+class ChartsAssetCard extends StatelessWidget {
+ const ChartsAssetCard(
+ {super.key,
+ required this.currency,
+ required this.price,
+ required this.ticker,
+ this.changeData,
+ required this.favorite,
+ required this.isSingleCurrency,});
+
+ final CryptoCurrency currency;
+ final String price;
+ final String ticker;
+ final PriceChangeData? changeData;
+ final bool favorite;
+ final bool isSingleCurrency;
+
+ String get displayPrice {
+ try {
+ final priceDouble = double.parse(price);
+ if (priceDouble > 10000) {
+ return priceDouble.toStringAsFixed(0);
+ } else {
+ return priceDouble.toStringAsFixed(2);
+ }
+ } catch (_) {
+ return price;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) => LongPressPopupBuilder(
+ popup: LongPressMenu(items: [
+ LongPressMenuItem(
+ label: S.of(context).favorite,
+ iconPath: "assets/new-ui/favorite.svg",
+ onSelected: () {
+ context.read<ChartsBloc>().add(CurrencyPinned(currency: currency));
+ Navigator.of(context).pop();
+ },
+ color: favorite ? Theme.of(context).colorScheme.error : null,),
+ LongPressMenuItem(
+ label: S.of(context).remove,
+ iconPath: "assets/new-ui/address_hide.svg",
+ onSelected: () {
+ if (!isSingleCurrency) {
+ context.read<ChartsBloc>().add(CurrencyRemoved(currency: currency));
+ Navigator.of(context).pop();
+ }
+ },
+ color: isSingleCurrency ? Theme.of(context).colorScheme.onSurfaceVariant : null,),
+ ],),
+ footer:
+ isSingleCurrency ? LongPressFooter(text: S.of(context).cannot_remove_last_asset) : null,
+ child: GestureDetector(
+ onTap: () async {
+ unawaited(HapticFeedback.mediumImpact());
+ final res = await showModalBottomSheet(
+ isScrollControlled: true,
+ context: context,
+ builder: (context) => ChartModal(
+ currency: currency,
+ isFavorite: favorite,
+ ),);
+ if (res != null && res is bool && res) {
+ context.read<ChartsBloc>().add(CurrencyPinned(currency: currency));
+ }
+ },
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(16),
+ border: Border.all(
+ width: 1, color: Theme.of(context).colorScheme.surfaceContainerHighest,),
+ gradient: LinearGradient(colors: [
+ context.customColors.cardGradientColorPrimary,
+ context.customColors.cardGradientColorSecondary,
+ ], begin: Alignment.topCenter, end: Alignment.bottomCenter,),),
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Column(
+ spacing: 12,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ spacing: 5,
+ children: [
+ if (changeData != null)
+ RotatedBox(
+ quarterTurns:
+ changeData!.direction == PriceChangeDirection.up ? 0 : 2,
+ child: CakeImageWidget(
+ imageUrl: "assets/new-ui/price_change_arrow.svg",
+ width: 8,
+ height: 8,
+ colorFilter: ColorFilter.mode(
+ changeData!.direction.color, BlendMode.srcIn,),
+ ),
+ ),
+ Text(
+ currency.title.toUpperCase(),
+ style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
+ ),
+ ],
+ ),
+ Text(
+ currency.fullName ?? "",
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontSize: 12,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,),
+ ),
+ ],
+ ),
+ ),
+ if (changeData != null)
+ ChangePill(
+ changePercentage: changeData!.percentage,
+ direction: changeData!.direction,),
+ ],
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ spacing: 8,
+ children: [
+ CakeImageWidget(
+ imageUrl: currency.iconPath,
+ width: 24,
+ height: 24,
+ ),
+ Expanded(
+ child: FittedBox(
+ alignment: Alignment.centerRight,
+ fit: BoxFit.scaleDown,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ spacing: 4,
+ children: [
+ Text(
+ displayPrice,
+ style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
+ ),
+ Text(
+ ticker,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ fontWeight: FontWeight.w500,
+ fontSize: 20,),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+}
### lib/new-ui/widgets/charts_page/asset_grid_header.dart
@@ -0,0 +1,85 @@
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_data_sort_criteria.dart";
+import "package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart";
+import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart";
+import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart";
+import "package:cake_wallet/new-ui/widgets/long_press_menu/long_press_menu.dart";
+import "package:cake_wallet/new-ui/widgets/long_press_menu/long_press_popup.dart";
+import "package:cake_wallet/new-ui/widgets/modern_button.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:flutter/material.dart";
+import "package:flutter_bloc/flutter_bloc.dart";
+
+class ChartsAssetGridHeader extends StatelessWidget {
+ const ChartsAssetGridHeader({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) => BlocBuilder<ChartsBloc, ChartsState>(
+ builder: (context, state) {
+ final bloc = context.read<ChartsBloc>();
+
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ S.of(context).followed_assets,
+ style:
+ TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12),
+ ),
+ Row(
+ spacing: 8,
+ children: [
+ ModernButton.svg(
+ size: 36,
+ iconSize: 16,
+ svgPath: "assets/new-ui/add.svg",
+ onPressed: () => _showCurrencyPicker(context, bloc),
+ ),
+ LongPressPopupBuilder(
+ showOnTap: true,
+ popup: LongPressMenu(
+ items: PriceDataSortCriterium.all.map((item) {
+ final isSelected =
+ state is ChartsStateWithData && state.sortCriterium == item;
+
+ return LongPressMenuItem(
+ label: item.name,
+ iconPath: item.iconPath,
+ color: isSelected ? Theme.of(context).colorScheme.primary : null,
+ onSelected: () {
+ bloc.add(SortingCriteriumChanged(newCriterium: item));
+ Navigator.of(context).pop();
+ },
+ );
+ }).toList(),
+ ),
+ child: ModernButton.svg(
+ size: 36,
+ iconSize: 12,
+ svgPath: "assets/new-ui/sort.svg",
+ onPressed: () {},
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+ },
+ );
+
+ void _showCurrencyPicker(BuildContext context, ChartsBloc bloc) {
+ CurrencyPickerSheet.show(
+ context: context,
+ args: CurrencyPickerArgs(
+ items: CryptoCurrency.all,
+ onSelected: (item) {
+ bloc.add(CurrencyAdded(currency: item));
+ },
+ showStablesHeader: false,
+ symbolResolver: (item) => item.symbol,
+ ),
+ );
+ }
+}
### lib/new-ui/widgets/charts_page/change_display.dart
@@ -0,0 +1,23 @@
+import 'package:cake_wallet/new-ui/model/charts/util/price_change_data.dart';
+import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart';
+import 'package:flutter/material.dart';
+
+class ChangeDisplay extends StatelessWidget {
+ const ChangeDisplay({super.key, required this.changeData, required this.ticker});
+
+ final String ticker;
+ final PriceChangeData changeData;
+
+ @override
+ Widget build(BuildContext context) => Row(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 10,
+ children: [
+ Text(
+ "${changeData.direction.symbol}${ticker} ${changeData.amount.toStringWithPrecision(fractionalDigits: 2)}",
+ style: TextStyle(fontSize: 16, color: changeData.direction.color),
+ ),
+ ChangePill(changePercentage: changeData.percentage, direction: changeData.direction)
+ ],
+ );
+}
### lib/new-ui/widgets/charts_page/change_pill.dart
@@ -0,0 +1,21 @@
+import "package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart";
+import "package:flutter/material.dart";
+
+class ChangePill extends StatelessWidget {
+ const ChangePill({super.key, required this.changePercentage, required this.direction});
+
+ final String changePercentage;
+ final PriceChangeDirection direction;
+
+ @override
+ Widget build(BuildContext context) => Container(
+ decoration: BoxDecoration(
+ color: direction.color.withAlpha(52), borderRadius: BorderRadius.circular(999999)),
+ child: Padding(
+ padding: const EdgeInsets.only(top: 2.5, bottom: 2.5, left: 4, right: 8),
+ child: Text(
+ "${direction.symbol} $changePercentage%",
+ style: TextStyle(color: direction.color),
+ ),
+ ));
+}
### lib/new-ui/widgets/charts_page/chart_header.dart
@@ -0,0 +1,162 @@
+import "package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/change_display.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/coin_header.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/price_header.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/range_selector.dart";
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:cw_core/amount/money_double.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:flutter/cupertino.dart";
+import "package:flutter/material.dart";
+import "package:flutter/services.dart";
+import "package:flutter_bloc/flutter_bloc.dart";
+import "package:intl/intl.dart";
+
+class ChartHeader extends StatefulWidget {
+ const ChartHeader({
+ super.key,
+ required this.currency,
+ required this.chartHeight,
+ required this.chartPadding,
+ required this.centered,
+ required this.favorite,
+ });
+
+ final CryptoCurrency currency;
+ final double chartHeight;
+ final double chartPadding;
+ final bool centered;
+ final bool favorite;
+
+ @override
+ State<ChartHeader> createState() => _ChartHeaderState();
+}
+
+class _ChartHeaderState extends State<ChartHeader> {
+ String? _viewedPrice;
+ String? _viewedTime;
+
+ @override
+ Widget build(BuildContext context) => BlocBuilder<ChartsBloc, ChartsState>(
+ builder: (context, state) {
+ if (state case final ChartsStateWithData s) {
+ return Column(
+ crossAxisAlignment:
+ widget.centered ? CrossAxisAlignment.center : CrossAxisAlignment.start,
+ spacing: 10,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 18),
+ child: Column(
+ crossAxisAlignment:
+ widget.centered ? CrossAxisAlignment.center : CrossAxisAlignment.start,
+ spacing: 10,
+ children: [
+ if (!widget.favorite)
+ CakeImageWidget(
+ imageUrl: widget.currency.iconPath ?? "",
+ width: 60,
+ height: 60,
+ ),
+ ChartViewCoinHeader(currency: widget.currency, isFavorite: widget.favorite),
+ ChartViewPriceHeader(
+ price: _viewedPrice ?? s.priceFor(widget.currency)?.toStringWithPrecision(fractionalDigits: 2) ?? "...",
+ ticker: s.fiatTicker,
+ highlight: _viewedPrice != null,
+ ),
+ Column(
+ crossAxisAlignment:
+ widget.centered ? CrossAxisAlignment.center : CrossAxisAlignment.start,
+ children: [
+ Container(
+ // has to be constant-size, otherwise will jump around when loading
+ height: 36,
+ alignment: widget.centered ? Alignment.center : Alignment.centerLeft,
+ child: (s is ChartsLoaded)
+ ? (_viewedPrice != null && _viewedTime != null)
+ ? Text(
+ _viewedTime!,
+ style: TextStyle(
+ fontFamily: "IBM Plex Mono",
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ fontSize: 16,
+ ),
+ )
+ : ChangeDisplay(
+ changeData: s.changeDataFor(widget.currency),
+ ticker: s.fiatTicker,
+ )
+ : const SizedBox.shrink(),
+ ),
+ if (s is ChartsLoaded)
+ Padding(
+ padding: EdgeInsets.symmetric(vertical: widget.chartPadding),
+ child: PriceChart(
+ height: widget.chartHeight,
+ prices: s.dataFor(widget.currency),
+ direction: s.changeDataFor(widget.currency).direction,
+ touchCallback: (event, response) {
+ if (!event.isInterestedForInteractions) {
+ setState(() {
+ _viewedPrice = null;
+ _viewedTime = null;
+ });
+ return;
+ }
+
+ final newPrice =
+ response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2);
+ final newTime = DateFormat("M/d/y HH:mm").format(
+ DateTime.fromMillisecondsSinceEpoch(
+ response?.lineBarSpots?.firstOrNull?.x.toInt() ?? 0,
+ ),
+ );
+ if (_viewedPrice != newPrice) {
+ HapticFeedback.selectionClick();
+ setState(() {
+ _viewedPrice = newPrice;
+ });
+ }
+ if (_viewedTime != newTime) {
+ setState(() {
+ _viewedTime = newTime;
+ });
+ }
+ },
+ ),
+ )
+ else
+ SizedBox(
+ height: widget.chartHeight + widget.chartPadding * 2,
+ child: const Center(child: CupertinoActivityIndicator()),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Container(
+ width: double.infinity,
+ height: 1,
+ color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(128),
+ ),
+ Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ ChartRangeSelector(
+ selectedRange: s.range,
+ onRangeSelected: (range) =>
+ context.read<ChartsBloc>().add(RangeChanged(newRange: range)),
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+ return const CupertinoActivityIndicator();
+ },
+ );
+}
### lib/new-ui/widgets/charts_page/chart_modal.dart
@@ -0,0 +1,157 @@
+import "package:cake_wallet/di.dart";
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/main.dart";
+import "package:cake_wallet/new-ui/modal_navigator.dart";
+import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart";
+import "package:cake_wallet/new-ui/pages/swap_page.dart";
+import "package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart";
+import "package:cake_wallet/new-ui/widgets/charts_page/chart_header.dart";
+import "package:cake_wallet/new-ui/widgets/coins_page/action_row/coin_action_button.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
+import "package:cake_wallet/routes.dart";
+import "package:cake_wallet/src/screens/buy/buy_sell_page.dart";
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:cake_wallet/store/app_store.dart";
+import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:flutter/material.dart";
+import "package:flutter_bloc/flutter_bloc.dart";
+import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
+
+class ChartModal extends StatelessWidget {
+ const ChartModal({required this.currency, required this.isFavorite, super.key});
+
+ final CryptoCurrency currency;
+ final bool isFavorite;
+
+ @override
+ Widget build(BuildContext context) => BlocProvider(
+ create: (context) => getIt.get<ChartsBloc>()..add(Init()),
+ child: Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
+ borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
+ ),
+ child: SafeArea(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ModalTopBar(
+ title: "",
+ onLeadingPressed: Navigator.of(context).pop,
+ leadingIcon: const Icon(Icons.close),
+ padding: const EdgeInsets.only(top: 18, left: 18),
+ ),
+ ChartHeader(
+ currency: currency,
+ chartHeight: 220,
+ chartPadding: 32,
+ centered: true,
+ favorite: false,
+ ),
+ const SizedBox(
+ height: 48,
+ ),
+ Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: MediaQuery.of(context).size.width * 0.05,
+ children: [
+ if (canOpenBuySell) ...[
+ CoinActionButton(
+ icon: CakeImageWidget(
+ width: 36,
+ height: 36,
+ imageUrl: "assets/new-ui/plus.svg",
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.primary,
+ BlendMode.srcIn,
+ ),
+ ),
+ label: S.of(context).buy,
+ action: () => openBuySellPage(context, BuySellPageMode.buy),
+ ),
+ CoinActionButton(
+ icon: CakeImageWidget(
+ width: 36,
+ height: 36,
+ imageUrl: "assets/new-ui/sell.svg",
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.primary,
+ BlendMode.srcIn,
+ ),
+ ),
+ label: S.of(context).sell,
+ action: () => openBuySellPage(context, BuySellPageMode.sell),
+ ),
+ ],
+ CoinActionButton(
+ icon: CakeImageWidget(
+ imageUrl: "assets/new-ui/exchange.svg",
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.primary,
+ BlendMode.srcIn,
+ ),
+ ),
+ label: S.of(context).swap,
+ action: () {
+ final page = getIt.get<NewSwapPage>(param2: currency);
+ showCupertinoModalBottomSheet(
+ context: context,
+ barrierColor: Colors.black.withAlpha(85),
+ builder: (context) => Material(
+ child: ModalNavigator(
+ rootPage: page,
+ parentContext: context,
+ ),
+ ),
+ );
+ },
+ ),
+ CoinActionButton(
+ icon: CakeImageWidget(
+ width: 36,
+ height: 36,
+ imageUrl: "assets/new-ui/favorite.svg",
+ colorFilter: ColorFilter.mode(
+ isFavorite
+ ? Theme.of(context).colorScheme.onSurface
+ : Theme.of(context).colorScheme.primary,
+ BlendMode.srcIn,
+ ),
+ ),
+ gradientColors:
+ isFavorite ? [const Color(0xFFDF2626), const Color(0xFF980F0F)] : null,
+ label: S.of(context).favorite,
+ action: () {
+ Navigator.of(context).pop(true);
+ },
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 32,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ void openBuySellPage(BuildContext context, BuySellPageMode mode) {
+ Navigator.of(context).pop();
+ final page = getIt.get<NewBuySellAmountPage>(
+ param1: NewBuySellParams(mode: mode, initialCurrency: currency));
+ showModalBottomSheet(
+ useSafeArea: true,
+ isScrollControlled: true,
+ context: navigatorKey.currentContext!,
+ builder: (modalContext) => ModalNavigator(
+ rootPage: page,
+ parentContext: modalContext,
+ ),
+ );
+ }
+
+ bool get canOpenBuySell => getIt.get<AppStore>().wallet!.balance.containsKey(currency);
+}
### lib/new-ui/widgets/charts_page/chart_view.dart
@@ -0,0 +1,70 @@
+import "package:cake_wallet/new-ui/model/charts/price_data.dart";
+import "package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart";
+import "package:fl_chart/fl_chart.dart";
+import "package:flutter/material.dart";
+
+class PriceChart extends StatelessWidget {
+ const PriceChart({
+ super.key,
+ required this.height,
+ required this.prices,
+ required this.direction,
+ required this.touchCallback,
+ });
+
+ final List<PriceData> prices;
+ final double height;
+ final PriceChangeDirection direction;
+ final Function(FlTouchEvent, LineTouchResponse?) touchCallback;
+
+ @override
+ Widget build(BuildContext context) {
+ final chartPoints = prices.map((entry) {
+ final x = entry.time.millisecondsSinceEpoch.toDouble();
+ final y = double.parse(entry.quote.toString());
+ return FlSpot(x, y);
+ }).toList();
+
+ return SizedBox(
+ height: height,
+ child: LineChart(
+ LineChartData(
+ gridData: const FlGridData(show: false),
+ titlesData: const FlTitlesData(show: false),
+ borderData: FlBorderData(show: false),
+ lineBarsData: [
+ LineChartBarData(
+ spots: chartPoints,
+ gradient: LinearGradient(colors: [direction.color.withAlpha(25), direction.color]),
+ barWidth: 1.5,
+ isStrokeCapRound: true,
+ dotData: const FlDotData(show: false),
+ belowBarData: BarAreaData(show: false),
+ ),
+ ],
+ lineTouchData: LineTouchData(
+ enabled: true,
+ getTouchLineStart: (barData, spotIndex) => -double.infinity,
+ getTouchLineEnd: (barData, spotIndex) => double.infinity,
+ getTouchedSpotIndicator: (barData, spotIndexes) => spotIndexes
+ .map(
+ (index) => TouchedSpotIndicatorData(
+ FlLine(
+ strokeWidth: 1,
+ color: direction.color.withAlpha(80),
+ dashArray: [4, 4],
+ ),
+ const FlDotData(),
+ ),
+ )
+ .toList(),
+ touchTooltipData: LineTouchTooltipData(
+ getTooltipItems: (touchedSpots) => touchedSpots.map((spot) => null).toList(),
+ ),
+ touchCallback: touchCallback,
+ ),
+ ),
+ ),
+ );
+ }
+}
### lib/new-ui/widgets/charts_page/coin_header.dart
@@ -0,0 +1,67 @@
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:flutter/material.dart";
+
+class ChartViewCoinHeader extends StatelessWidget {
+ const ChartViewCoinHeader({required this.currency, required this.isFavorite, super.key});
+
+ final CryptoCurrency currency;
+ final bool isFavorite;
+
+ @override
+ Widget build(BuildContext context) => Row(
+ mainAxisAlignment: isFavorite ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 10,
+ children: [
+ if (isFavorite)
+ CakeImageWidget(
+ imageUrl: currency.iconPath ?? "",
+ width: 30,
+ height: 30,
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 5,
+ children: [
+ Text(
+ currency.fullName ?? currency.title,
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).colorScheme.onSurface,
+ ),
+ ),
+ Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(9999999),
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+ child: Text(
+ currency.title,
+ style: TextStyle(
+ fontSize: 14,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ if (isFavorite)
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/favorite.svg",
+ width: 16,
+ height: 16,
+ colorFilter:
+ ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
+ ),
+ ],
+ );
+}
### lib/new-ui/widgets/charts_page/price_header.dart
@@ -0,0 +1,43 @@
+import "package:flutter/material.dart";
+
+class ChartViewPriceHeader extends StatelessWidget {
+ const ChartViewPriceHeader({
+ required this.price,
+ required this.ticker,
+ required this.highlight,
+ super.key,
+ });
+
+ final String price;
+ final String ticker;
+ final bool highlight;
+
+ @override
+ Widget build(BuildContext context) => FittedBox(
+ fit: BoxFit.scaleDown,
+ alignment: Alignment.centerLeft,
+ child: Row(
+ spacing: 6,
+ children: [
+ Text(
+ price,
+ style: TextStyle(
+ fontSize: 36,
+ color: highlight
+ ? Theme.of(context).colorScheme.primary
+ : Theme.of(context).colorScheme.onSurface,
+ ),
+ ),
+ Text(
+ ticker,
+ style: TextStyle(
+ fontSize: 36,
+ color: highlight
+ ? Theme.of(context).colorScheme.primary.withAlpha(128)
+ : Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ ),
+ ],
+ ),
+ );
+}
### lib/new-ui/widgets/charts_page/range_selector.dart
@@ -0,0 +1,72 @@
+import "package:cake_wallet/new-ui/model/charts/util/chart_range.dart";
+import "package:flutter/material.dart";
+
+class ChartRangeSelector extends StatelessWidget {
+ const ChartRangeSelector({required this.selectedRange, required this.onRangeSelected, super.key});
+
+ final ChartRange selectedRange;
+ final Function(ChartRange) onRangeSelected;
+
+ static const double optionSize = 36;
+ static const double optionPadding = 24;
+ static const Duration switchDuration = Duration(milliseconds: 250);
+
+ double pillPosition(int selectedIndex) => selectedIndex * (optionSize + optionPadding);
+
+ @override
+ Widget build(BuildContext context) {
+ final selectedIndex = ChartRange.ranges.indexOf(selectedRange);
+
+ return Stack(
+ children: [
+ AnimatedPositioned(
+ curve: Curves.easeOutCubic,
+ left: pillPosition(selectedIndex),
+ duration: switchDuration,
+ child: Container(
+ height: optionSize,
+ width: optionSize,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.onSurface.withAlpha(25),
+ borderRadius: BorderRadius.circular(999999),
+ ),
+ ),
+ ),
+ Row(
+ children: ChartRange.ranges.map((item) {
+ final selected = selectedRange == item;
+ final isFirst = ChartRange.ranges.indexOf(item) == 0;
+ return GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: () {
+ if (!selected) {
+ onRangeSelected(item);
+ }
+ },
+ child: Padding(
+ padding: EdgeInsets.only(
+ right: optionPadding / 2,
+ left: isFirst ? 0 : optionPadding / 2,
+ ),
+ child: SizedBox(
+ width: optionSize,
+ height: optionSize,
+ child: AnimatedDefaultTextStyle(
+ duration: switchDuration,
+ style: TextStyle(
+ fontWeight: selected ? FontWeight.w400 : FontWeight.w500,
+ color: selected
+ ? Theme.of(context).colorScheme.onSurface
+ : Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ child: Center(child: Text(item.displayText)),
+ ),
+ ),
+ ),
+ );
+ }).toList(),
+ ),
+ ],
+ );
+ }
+}
### lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart
@@ -1,20 +1,22 @@
-import 'dart:math';
+import "dart:math";
-import 'package:cake_wallet/themes/core/theme_extension.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
+import "package:cake_wallet/themes/core/theme_extension.dart";
+import "package:flutter/material.dart";
+import "package:flutter/services.dart";
class CoinActionButton extends StatelessWidget {
const CoinActionButton({
- super.key,
required this.icon,
required this.label,
required this.action,
+ super.key,
+ this.gradientColors,
});
final Widget icon;
final String label;
final VoidCallback action;
+ final List<Color>? gradientColors;
static const sizeFactor = 0.16;
@@ -37,10 +39,11 @@ class CoinActionButton extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
- colors: [
- context.customColors.cardGradientColorPrimary,
- context.customColors.cardGradientColorSecondary
- ],
+ colors: gradientColors ??
+ [
+ context.customColors.cardGradientColorPrimary,
+ context.customColors.cardGradientColorSecondary,
+ ],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
@@ -61,7 +64,7 @@ class CoinActionButton extends StatelessWidget {
),
),
Padding(
- padding: const EdgeInsets.only(top: 8.0),
+ padding: const EdgeInsets.only(top: 8),
child: ExcludeSemantics(
child: Text(
style: Theme.of(context).textTheme.bodySmall?.copyWith(
### lib/new-ui/widgets/currency_picker/currency_picker_args.dart
@@ -118,6 +118,7 @@ class CurrencyPickerArgs {
const CurrencyPickerArgs({
this.selected,
required this.items,
+ this.showStablesHeader = true,
this.balanceByAsset,
this.filterByNetwork,
required this.onSelected,
@@ -129,6 +130,7 @@ class CurrencyPickerArgs {
final CryptoCurrency? selected;
final List<CryptoCurrency> items;
final WalletType? filterByNetwork;
+ final bool showStablesHeader;
final void Function(CryptoCurrency) onSelected;
final String Function(CryptoCurrency) symbolResolver;
final Map<CryptoCurrency, CurrencyPickerBalance>? balanceByAsset;
### lib/new-ui/widgets/currency_picker/multi_network_currency_picker.dart
@@ -166,6 +166,7 @@ class _MultiNetworkCurrencyPickerState extends State<MultiNetworkCurrencyPicker>
natives: _natives,
selected: widget.args.selected,
symbolResolver: widget.args.symbolResolver,
+ showStablesHeader: widget.args.showStablesHeader,
onSelect: _selectCurrency,
onStablecoinTap: _onStablecoinPillTapped,
),
@@ -190,10 +191,12 @@ class _MultiNetworkPickerBody extends StatefulWidget {
required this.symbolResolver,
required this.onSelect,
required this.onStablecoinTap,
+ required this.showStablesHeader,
});
final bool isSearching;
final bool recentsLoaded;
+ final bool showStablesHeader;
final CryptoCurrency? selected;
final List<CryptoCurrency> items;
final List<CryptoCurrency> recents;
@@ -363,7 +366,7 @@ class _MultiNetworkPickerBodyState extends State<_MultiNetworkPickerBody> {
onTap: onSelect,
),
),
- if (stablecoins.isNotEmpty)
+ if (stablecoins.isNotEmpty && widget.showStablesHeader)
_PickerSection(
title: S.of(context).picker_section_stablecoins,
child: PillGrid(
### lib/new-ui/widgets/long_press_menu.dart
@@ -1,108 +0,0 @@
-import 'dart:ui';
-
-import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_svg/svg.dart';
-
-class LongPressMenuItem {
- final String label;
- final String iconPath;
- final VoidCallback onSelected;
- final Color? color;
-
- LongPressMenuItem(
- {required this.label, required this.iconPath, required this.onSelected, this.color});
-}
-
-class LongPressMenu extends StatefulWidget {
- const LongPressMenu({super.key, required this.items});
-
- final List<LongPressMenuItem> items;
-
- @override
- State<LongPressMenu> createState() => _LongPressMenuState();
-}
-
-class _LongPressMenuState extends State<LongPressMenu> {
- bool _isVisible = false;
-
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addPostFrameCallback((_) {
- setState(() {
- _isVisible = true;
- });
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return AnimatedSize(
- duration: Duration(milliseconds: 300),
- curve: Curves.easeOutCubic,
- alignment: Alignment.topCenter,
- child: SizedBox(
- height: _isVisible ? null : 0,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(20),
- child: BackdropFilter(
- filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
- child: Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20),
- color: Theme.of(context).colorScheme.primary.withAlpha(60),
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: widget.items.map((item) {
- final color = item.color ?? Theme.of(context).colorScheme.onSurface;
- return Material(
- color: Colors.transparent,
- child: MergeSemantics(
- child: Semantics(
- button: true,
- child: InkWell(
- onTap: item.onSelected,
- child: Padding(
- padding: EdgeInsets.only(
- left: 16,
- right: 16,
- top: 12,
- bottom: 12,
- ),
- child: Container(
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.start,
- spacing: 8,
- children: [
- ExcludeSemantics(
- child: CakeImageWidget(
- imageUrl: item.iconPath,
- height: 20,
- width: 20,
- colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
- ),
- ),
- Text(item.label,
- style: TextStyle(
- color: color, fontSize: 14, fontWeight: FontWeight.w500)),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- );
- }).toList(),
- ),
- ),
- ),
- ),
- ),
- );
- }
-}
### lib/new-ui/widgets/long_press_menu/long_press_footer.dart
@@ -0,0 +1,42 @@
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:flutter/material.dart";
+
+class LongPressFooter extends StatelessWidget {
+ const LongPressFooter({required this.text, super.key});
+
+ final String text;
+
+ @override
+ Widget build(BuildContext context) => Padding(
+ padding: const EdgeInsets.all(12),
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(20),
+ color: Theme.of(context).colorScheme.primary.withAlpha(60),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 12,
+ children: [
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/info.svg",
+ width: 18,
+ height: 18,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn,
+ ),
+ ),
+ Text(
+ text,
+ style: TextStyle(
+ fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+}
### lib/new-ui/widgets/long_press_menu/long_press_menu.dart
@@ -0,0 +1,112 @@
+import "dart:ui";
+
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:flutter/material.dart";
+
+class LongPressMenuItem {
+ LongPressMenuItem({
+ required this.label,
+ required this.iconPath,
+ required this.onSelected,
+ this.color,
+ });
+
+ final String label;
+ final String iconPath;
+ final VoidCallback onSelected;
+ final Color? color;
+}
+
+class LongPressMenu extends StatefulWidget {
+ const LongPressMenu({required this.items, super.key});
+
+ final List<LongPressMenuItem> items;
+
+ @override
+ State<LongPressMenu> createState() => _LongPressMenuState();
+}
+
+class _LongPressMenuState extends State<LongPressMenu> {
+ bool _isVisible = false;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ setState(() {
+ _isVisible = true;
+ });
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) => AnimatedSize(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.easeOutCubic,
+ alignment: Alignment.topCenter,
+ child: SizedBox(
+ height: _isVisible ? null : 0,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(20),
+ child: BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(20),
+ color: Theme.of(context).colorScheme.primary.withAlpha(60),
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: widget.items.map((item) {
+ final color = item.color ?? Theme.of(context).colorScheme.onSurface;
+ return Material(
+ color: Colors.transparent,
+ child: MergeSemantics(
+ child: Semantics(
+ button: true,
+ child: InkWell(
+ onTap: item.onSelected,
+ child: Padding(
+ padding: const EdgeInsets.only(
+ left: 16,
+ right: 16,
+ top: 12,
+ bottom: 12,
+ ),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.start,
+ spacing: 8,
+ children: [
+ ExcludeSemantics(
+ child: CakeImageWidget(
+ imageUrl: item.iconPath,
+ height: 20,
+ width: 20,
+ colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
+ ),
+ ),
+ Text(
+ item.label,
+ style: TextStyle(
+ color: color,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }).toList(),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
### lib/new-ui/widgets/long_press_menu/long_press_popup.dart
@@ -0,0 +1,80 @@
+import "dart:ui";
+
+import "package:cake_wallet/utils/show_pop_up.dart";
+import "package:flutter/material.dart";
+
+class LongPressPopupBuilder extends StatelessWidget {
+ const LongPressPopupBuilder({
+ required this.child,
+ required this.popup,
+ super.key,
+ this.spacing = 8,
+ this.showOnTap = false,
+ this.footer,
+ });
+
+ final Widget child;
+ final Widget popup;
+ final Widget? footer;
+ final double spacing;
+ final bool showOnTap;
+
+ @override
+ Widget build(BuildContext context) => GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ onLongPress: () => _showMenu(context),
+ onTap: showOnTap ? () => _showMenu(context) : null,
+ child: IgnorePointer(ignoring: showOnTap, child: child),
+ );
+
+ void _showMenu(BuildContext context) {
+ final RenderBox renderBox = context.findRenderObject()! as RenderBox;
+ final offset = renderBox.localToGlobal(Offset.zero);
+ final size = renderBox.size;
+
+ showPopUp(
+ context: context,
+ builder: (context) => _buildPopup(context, offset, size),
+ );
+ }
+
+ Widget _buildPopup(BuildContext context, Offset offset, Size size) {
+ final screenWidth = MediaQuery.of(context).size.width;
+
+ final bool isOnRightHalf = offset.dx + (size.width / 2) > screenWidth / 2;
+
+ return BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
+ child: Stack(
+ children: [
+ Positioned(
+ left: offset.dx,
+ top: offset.dy,
+ width: size.width,
+ height: size.height,
+ child: Material(
+ color: Colors.transparent,
+ child: child,
+ ),
+ ),
+ Positioned(
+ left: isOnRightHalf ? null : offset.dx,
+ right: isOnRightHalf ? (screenWidth - (offset.dx + size.width)) : null,
+ top: offset.dy + size.height + spacing,
+ child: Material(
+ color: Colors.transparent,
+ child: popup,
+ ),
+ ),
+ if (footer != null)
+ Positioned(
+ left: 0,
+ right: 0,
+ bottom: MediaQuery.of(context).viewPadding.bottom,
+ child: footer!,
+ ),
+ ],
+ ),
+ );
+ }
+}
### lib/router.dart
@@ -663,7 +663,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.buySellPage:
return handleRouteWithPlatformAwareness(
- (context) => Material(child: getIt.get<NewBuySellAmountPage>(param1: BuySellPageMode.buy)),
+ (context) => Material(child: getIt.get<NewBuySellAmountPage>(param1: NewBuySellParams(mode: BuySellPageMode.buy))),
);
### lib/src/screens/buy/buy_sell_page.dart
[binary or diff unavailable]
### lib/src/screens/dashboard/widgets/new_main_navbar_widget.dart
@@ -51,7 +51,7 @@ class _NEWNewMainNavBarState extends State<NewMainNavBar> {
static const iconColorChangeDuration = Duration(milliseconds: 200);
static const pillTextStyle = TextStyle(
- fontSize: 14,
+ fontSize: 16,
fontWeight: FontWeight.w500,
);
### lib/view_model/buy/buy_sell_view_model.dart
@@ -12,6 +12,7 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart";
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/app_store.dart';
@@ -30,9 +31,10 @@ enum BuySellPageMode { buy, sell }
class BuySellViewModel = BuySellViewModelBase with _$BuySellViewModel;
abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with Store {
- BuySellViewModelBase(AppStore appStore, {required this.mode, required this.fiatConversionStore})
+ BuySellViewModelBase(AppStore appStore, {required NewBuySellParams params, required this.fiatConversionStore})
: _cryptoAmount = '',
fiatAmount = '',
+ mode = params.mode,
cryptoCurrencyAddress = '',
isCryptoCurrencyAddressEnabled = false,
cryptoCurrencies = <CryptoCurrency>[],
@@ -58,6 +60,10 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
_initialize();
isCryptoCurrencyAddressEnabled = !(cryptoCurrency == wallet.currency);
+
+ if(params.initialCurrency != null) {
+ changeCryptoCurrency(currency: params.initialCurrency!);
+ }
}
late Timer bestRateSync;
### pubspec_base.yaml
@@ -166,7 +166,7 @@ dependencies:
torch_dart:
path: ./scripts/torch_dart
quick_actions: ^1.1.0
- fl_chart: ^0.70.2
+ fl_chart: ^1.2.0
dev_dependencies:
flutter_test:
@@ -313,6 +313,7 @@ flutter:
- assets/new-ui/node_speed_badges/
- assets/new-ui/address_sources/
- assets/new-ui/buy_payment_methods/
+ - assets/new-ui/charts_sort_criteria/
fonts:
- family: Wix Madefor Text
### res/pictures/add.svg
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M6.82418 8.6044H0V6.82418H6.82418V0H8.6044V6.82418H15.4286V8.6044H8.6044V15.4286H6.82418V8.6044Z" fill="white"/>
+</svg>
### res/pictures/buy.svg
@@ -0,0 +1,10 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <g clip-path="url(#clip0_18375_781447)">
+ <path d="M14.6406 32V28.1742C13.0488 27.8904 11.678 27.2686 10.5281 26.3086C9.37826 25.3486 8.52904 24.0059 7.98047 22.2807L10.5194 21.2256C11.0116 22.6415 11.713 23.7246 12.6237 24.475C13.5346 25.225 14.7508 25.6 16.2721 25.6C17.5921 25.6 18.7567 25.2859 19.7661 24.6578C20.7751 24.0294 21.2797 23.033 21.2797 21.6686C21.2797 20.4846 20.9005 19.5392 20.1423 18.8325C19.3837 18.1257 17.8148 17.3879 15.4356 16.619C12.9435 15.8312 11.2128 14.9099 10.2433 13.8551C9.27388 12.8 8.78915 11.4731 8.78915 9.87429C8.78915 8.03413 9.4351 6.58591 10.727 5.5296C12.0186 4.4736 13.3231 3.88693 14.6406 3.7696V0H17.3834V3.7696C18.7783 3.9549 19.9505 4.40853 20.8998 5.13051C21.8494 5.85249 22.5879 6.79147 23.1151 7.94743L20.6118 9.09348C20.2107 8.21211 19.6515 7.52762 18.9341 7.04C18.217 6.55238 17.2734 6.30857 16.1034 6.30857C14.7274 6.30857 13.6215 6.64381 12.7855 7.31429C11.9498 7.98476 11.532 8.83809 11.532 9.87429C11.532 10.9385 11.9599 11.7896 12.8157 12.4274C13.6711 13.065 15.2617 13.733 17.5873 14.4315C19.7487 15.088 21.3616 16.0235 22.4262 17.2379C23.4904 18.4521 24.0225 19.9102 24.0225 21.6123C24.0225 23.6119 23.3796 25.1637 22.0938 26.2679C20.8081 27.3723 19.2379 28.0265 17.3834 28.2304V32H14.6406Z" fill="white"/>
+ </g>
+ <defs>
+ <clipPath id="clip0_18375_781447">
+ <rect width="32" height="32" fill="white"/>
+ </clipPath>
+ </defs>
+</svg>
### res/pictures/charts_sort_criteria/alpha.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="white"><path d="m196-376-23 70q-4 11-14 18.5t-22 7.5q-20 0-32.5-16.5T100-333l120-321q5-12 15-19t23-7h30q13 0 23 7t15 19l121 323q7 19-4.5 35T411-280q-12 0-22-7.5T375-306l-25-70H196Zm24-68h104l-48-150h-6l-50 150Zm418 92h166q15 0 25.5 10.5T840-316q0 15-10.5 25.5T804-280H572q-10 0-17-7t-7-17v-38q0-7 2-13.5t7-11.5l193-241H592q-15 0-25.5-10.5T556-644q0-15 10.5-25.5T592-680h222q10 0 17 7t7 17v38q0 7-2 13.5t-7 11.5L638-352ZM384-760q-7 0-9.5-6t2.5-11l89-89q6-6 14-6t14 6l89 89q5 5 2.5 11t-9.5 6H384Zm82 666-89-89q-5-5-2.5-11t9.5-6h192q7 0 9.5 6t-2.5 11l-89 89q-6 6-14 6t-14-6Z"/></svg>
\ No newline at end of file
### res/pictures/charts_sort_criteria/gains.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="white"><path d="M108-255q-12-12-11.5-28.5T108-311l211-214q23-23 57-23t57 23l103 104 208-206h-64q-17 0-28.5-11.5T640-667q0-17 11.5-28.5T680-707h160q17 0 28.5 11.5T880-667v160q0 17-11.5 28.5T840-467q-17 0-28.5-11.5T800-507v-64L593-364q-23 23-57 23t-57-23L376-467 164-255q-11 11-28 11t-28-11Z"/></svg>
\ No newline at end of file
### res/pictures/charts_sort_criteria/losses.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="white"><path d="M744-320 536-526 433-423q-23 23-57 23t-57-23L108-636q-11-11-11.5-27.5T108-692q11-11 28-11t28 11l212 212 103-103q23-23 57-23t57 23l207 207v-64q0-17 11.5-28.5T840-480q17 0 28.5 11.5T880-440v160q0 17-11.5 28.5T840-240H680q-17 0-28.5-11.5T640-280q0-17 11.5-28.5T680-320h64Z"/></svg>
\ No newline at end of file
### res/pictures/charts_sort_criteria/marketcap.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="white"><path d="M336-120q-91 0-153.5-62.5T120-336q0-38 13-74t37-65l142-171-68-136q-10-20 1.5-39t34.5-19h400q23 0 34.5 19t1.5 39l-68 136 142 171q24 29 37 65t13 74q0 91-63 153.5T624-120H336Zm144-200q-33 0-56.5-23.5T400-400q0-33 23.5-56.5T480-480q33 0 56.5 23.5T560-400q0 33-23.5 56.5T480-320Zm-95-360h190l40-80H345l40 80Zm-49 480h288q57 0 96.5-39.5T760-336q0-24-8.5-46.5T728-423L581-600H380L232-424q-15 18-23.5 41t-8.5 47q0 57 39.5 96.5T336-200Z"/></svg>
\ No newline at end of file
### res/pictures/favorite.svg
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M7.83333 13.9512L7.0975 13.2897C5.99239 12.2871 5.0785 11.4256 4.35583 10.7052C3.63317 9.98461 3.0605 9.34333 2.63783 8.78133C2.21517 8.21944 1.91989 7.70683 1.752 7.2435C1.584 6.78028 1.5 6.31022 1.5 5.83333C1.5 4.88722 1.819 4.09511 2.457 3.457C3.09511 2.819 3.88722 2.5 4.83333 2.5C5.41533 2.5 5.96533 2.63611 6.48333 2.90833C7.00133 3.18056 7.45133 3.57094 7.83333 4.0795C8.21533 3.57094 8.66533 3.18056 9.18333 2.90833C9.70133 2.63611 10.2513 2.5 10.8333 2.5C11.7794 2.5 12.5716 2.819 13.2097 3.457C13.8477 4.09511 14.1667 4.88722 14.1667 5.83333C14.1667 6.31022 14.0827 6.78028 13.9147 7.2435C13.7468 7.70683 13.4515 8.21944 13.0288 8.78133C12.6062 9.34333 12.0346 9.98461 11.314 10.7052C10.5936 11.4256 9.67861 12.2871 8.56917 13.2897L7.83333 13.9512Z" fill="white"/>
+</svg>
### res/pictures/info.svg
@@ -1,3 +1,7 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M7.29167 11.875H8.54167V7.08333H7.29167V11.875ZM7.91667 5.65708C8.10736 5.65708 8.26722 5.59257 8.39625 5.46354C8.52528 5.33451 8.58979 5.17465 8.58979 4.98396C8.58979 4.79326 8.52528 4.6334 8.39625 4.50437C8.26722 4.37549 8.10736 4.31104 7.91667 4.31104C7.72597 4.31104 7.56611 4.37549 7.43708 4.50437C7.30806 4.6334 7.24354 4.79326 7.24354 4.98396C7.24354 5.17465 7.30806 5.33451 7.43708 5.46354C7.56611 5.59257 7.72597 5.65708 7.91667 5.65708ZM7.91812 15.8333C6.82312 15.8333 5.79389 15.6256 4.83042 15.21C3.86694 14.7944 3.02889 14.2305 2.31625 13.5181C1.60361 12.8058 1.03937 11.9681 0.623541 11.005C0.207847 10.0419 0 9.01299 0 7.91812C0 6.82312 0.207778 5.79389 0.623333 4.83042C1.03889 3.86694 1.60285 3.02889 2.31521 2.31625C3.02757 1.60361 3.86528 1.03937 4.82833 0.623541C5.79139 0.207847 6.82035 0 7.91521 0C9.01021 0 10.0394 0.207777 11.0029 0.623333C11.9664 1.03889 12.8044 1.60285 13.5171 2.31521C14.2297 3.02757 14.794 3.86528 15.2098 4.82833C15.6255 5.79139 15.8333 6.82035 15.8333 7.91521C15.8333 9.01021 15.6256 10.0394 15.21 11.0029C14.7944 11.9664 14.2305 12.8044 13.5181 13.5171C12.8058 14.2297 11.9681 14.794 11.005 15.2098C10.0419 15.6255 9.01299 15.8333 7.91812 15.8333ZM7.91667 14.5833C9.77778 14.5833 11.3542 13.9375 12.6458 12.6458C13.9375 11.3542 14.5833 9.77778 14.5833 7.91667C14.5833 6.05556 13.9375 4.47917 12.6458 3.1875C11.3542 1.89583 9.77778 1.25 7.91667 1.25C6.05556 1.25 4.47917 1.89583 3.1875 3.1875C1.89583 4.47917 1.25 6.05556 1.25 7.91667C1.25 9.77778 1.89583 11.3542 3.1875 12.6458C4.47917 13.9375 6.05556 14.5833 7.91667 14.5833Z" fill="white"/>
+<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="8" cy="8" r="7.375" stroke="#fff" stroke-width="1.25" />
+
+ <circle cx="7.9" cy="5.2" r="0.75" fill="#fff" />
+
+ <rect x="7.3" y="7.1" width="1.25" height="4.8" fill="#fff" />
</svg>
### res/pictures/price_change_arrow.svg
@@ -0,0 +1,3 @@
+<svg width="10" height="8" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M10 7.85495L5 0L0 7.85495L10 7.85495Z" fill="white"/>
+</svg>
### res/pictures/sort.svg
@@ -0,0 +1,3 @@
+<svg width="19" height="12" viewBox="0 0 19 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M7.38257 11.9798V10.3462H11.1211V11.9798H7.38257ZM3.16268 6.80672V5.17311H15.3413V6.80672H3.16268ZM0 1.63361V0H18.5143V1.63361H0Z" fill="white"/>
+</svg>
### res/values/strings_ar.arb
@@ -205,6 +205,7 @@
"change_username": "تغيير اسم المستخدم",
"change_wallet_alert_content": "هل تريد تغيير المحفظة الحالية إلى ${wallet_name}؟",
"change_wallet_alert_title": "تغيير المحفظة الحالية",
+ "charts": "الرسوم البيانية",
"check_receiver_forms": "يرجى التحقق من نماذج المستلم",
"choose_a_payment_method": "اختر طريقة دفع",
"choose_a_provider": "اختر مزوّد خدمة",
@@ -237,6 +238,8 @@
"color_theme": "سمة الألوان",
"coming_soon_tag": "قريبًا",
"commit_transaction_amount_fee": "تأكيد المعاملة\nالمبلغ: ${amount}\nالرسوم: ${fee}",
+ "compatible_services": "الخدمات المتوافقة",
+ "compatible_services_desc": "استخدم أداة المسح لقراءة الأنواع التالية من رموز QR المدعومة في Cake Wallet:",
"compatible_networks": "الشبكات المتوافقة",
"compatible_services": "الخدمات المتوافقة",
"compatible_services_desc": "استخدم أداة المسح لقراءة الأنواع التالية من رموز QR المدعومة في Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "هذا الزوج الثابت غير مدعوم مع خدمات المبادلة المحددة",
"fixed_rate": "سعر ثابت",
"fixed_rate_alert": "ستتمكن من إدخال مبلغ الاستلام عند تفعيل وضع السعر الثابت. هل تريد التبديل إلى وضع السعر الثابت؟",
+ "followed_assets": "الأصول المتبعه",
"forgot_password": "نسيت كلمة المرور",
"freeze": "تجميد",
"frequently_asked_questions": "الأسئلة الشائعة",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "جميع أحدث النماذج (GPT-5.3، Claude 4.6).\nبدون اشتراك، ادفع بالعملات المشفرة.",
"narrow": "ضيق",
"network": "الشبكة",
+ "networks": "الشبكات",
"network_address_detected": "تم اكتشاف عنوان ${network}.",
"network_prefix_on": "على",
"network_switch_failed": "فشل التبديل إلى الشبكة المطلوبة. لم يتم تطبيق الدفع، يرجى المحاولة مرة أخرى.",
### res/values/strings_bg.arb
@@ -205,6 +205,7 @@
"change_username": "Промяна на потребителското име",
"change_wallet_alert_content": "Искате ли да смените текущия портфейл с ${wallet_name}?",
"change_wallet_alert_title": "Смяна на текущия портфейл",
+ "charts": "Графики",
"check_receiver_forms": "Моля, проверете полетата за получателя",
"choose_a_payment_method": "Изберете метод на плащане",
"choose_a_provider": "Изберете доставчик",
@@ -237,6 +238,8 @@
"color_theme": "Цветова тема",
"coming_soon_tag": "Очаквайте скоро",
"commit_transaction_amount_fee": "Потвърдете транзакцията\nСума: ${amount}\nТакса: ${fee}",
+ "compatible_services": "Съвместими услуги",
+ "compatible_services_desc": "Използвайте инструмента за сканиране, за да четете следните типове QR кодове, поддържани в Cake Wallet:",
"compatible_networks": "Съвместими мрежи",
"compatible_services": "Съвместими услуги",
"compatible_services_desc": "Използвайте инструмента за сканиране, за да четете следните типове QR кодове, поддържани в Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Тази фиксирана двойка не се поддържа от избраните услуги за обмен",
"fixed_rate": "Фиксиран курс",
"fixed_rate_alert": "Ще можете да въведете сума за получаване, когато е отметнат режимът с фиксиран курс. Искате ли да превключите към режим с фиксиран курс?",
+ "followed_assets": "Следени активи",
"forgot_password": "Забравена парола",
"freeze": "Замразяване",
"frequently_asked_questions": "Често задавани въпроси",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Всички най-нови модели (GPT-5.3, Claude 4.6).\\nБез абонамент, плащайте с крипто.",
"narrow": "Тесен",
"network": "Мрежа",
+ "networks": "Мрежи",
"network_address_detected": "Открит адрес на ${network}",
"network_prefix_on": "на",
"network_switch_failed": "Неуспешно превключване към заявената мрежа. Плащането не е приложено, моля опитайте отново.",
### res/values/strings_cs.arb
@@ -205,6 +205,7 @@
"change_username": "Změnit uživatelské jméno",
"change_wallet_alert_content": "Chcete změnit aktuální peněženku na ${wallet_name}?",
"change_wallet_alert_title": "Změnit aktuální peněženku",
+ "charts": "Grafy",
"check_receiver_forms": "Zkontrolujte prosím údaje příjemce",
"choose_a_payment_method": "Vyberte způsob platby",
"choose_a_provider": "Vyberte poskytovatele",
@@ -237,6 +238,8 @@
"color_theme": "Barevné téma",
"coming_soon_tag": "Již brzy",
"commit_transaction_amount_fee": "Potvrdit transakci\nČástka: ${amount}\nPoplatek: ${fee}",
+ "compatible_services": "Kompatibilní služby",
+ "compatible_services_desc": "Pomocí nástroje pro skenování načtěte následující typy QR kódů podporované v Cake Wallet:",
"compatible_networks": "Kompatibilní sítě",
"compatible_services": "Kompatibilní služby",
"compatible_services_desc": "Pomocí nástroje pro skenování načtěte následující typy QR kódů podporované v Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Tento pevný pár není podporován u vybraných swapovacích služeb",
"fixed_rate": "Fixní kurz",
"fixed_rate_alert": "Když je zaškrtnutý režim pevného kurzu, budete moci zadat částku k přijetí. Chcete přepnout do režimu pevného kurzu?",
+ "followed_assets": "Sledovaná aktiva",
"forgot_password": "Zapomenuté heslo",
"freeze": "Zmrazit",
"frequently_asked_questions": "Často kladené otázky",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Všechny nejnovější modely (GPT-5.3, Claude 4.6).\nBez předplatného, plaťte kryptem.",
"narrow": "Úzké",
"network": "Síť",
+ "networks": "Sítě",
"network_address_detected": "Zjištěna adresa ${network}",
"network_prefix_on": "na",
"network_switch_failed": "Přepnutí na požadovanou síť se nezdařilo. Platba nebyla připsána, zkuste to prosím znovu.",
### res/values/strings_de.arb
@@ -206,6 +206,7 @@
"change_username": "Benutzernamen ändern",
"change_wallet_alert_content": "Möchten Sie zur Wallet ${wallet_name} wechseln?",
"change_wallet_alert_title": "Aktuelles Wallet ändern",
+ "charts": "Diagramme",
"check_receiver_forms": "Bitte überprüfen Sie die Empfängerangaben",
"choose_a_payment_method": "Wählen Sie eine Zahlungsmethode",
"choose_a_provider": "Anbieter auswählen",
@@ -238,6 +239,8 @@
"color_theme": "Farbschema",
"coming_soon_tag": "Demnächst verfügbar",
"commit_transaction_amount_fee": "Transaktion bestätigen\nBetrag: ${amount}\nGebühr: ${fee}",
+ "compatible_services": "Kompatible Dienste",
+ "compatible_services_desc": "Verwenden Sie das Scan-Tool, um die folgenden von Cake Wallet unterstützten QR-Code-Typen zu lesen:",
"compatible_networks": "Kompatible Netzwerke",
"compatible_services": "Kompatible Dienste",
"compatible_services_desc": "Verwenden Sie das Scan-Tool, um die folgenden von Cake Wallet unterstützten QR-Code-Typen zu lesen:",
@@ -528,6 +531,7 @@
"fixed_pair_not_supported": "Dieses feste Paar wird von den ausgewählten Swap-Diensten nicht unterstützt",
"fixed_rate": "Fester Kurs",
"fixed_rate_alert": "Sie können den Empfangsbetrag eingeben, wenn der Festkursmodus aktiviert ist. Möchten Sie zum Festkursmodus wechseln?",
+ "followed_assets": "Gefolgte Vermögenswerte",
"forgot_password": "Passwort vergessen",
"freeze": "Sperren",
"frequently_asked_questions": "Häufig gestellte Fragen",
@@ -711,6 +715,7 @@
"nanogpt_subtitle": "Alle neuesten Modelle (GPT-5.3, Claude 4.6).\nKein Abonnement – bezahlen Sie mit Krypto.",
"narrow": "Schmal",
"network": "Netzwerk",
+ "networks": "Netzwerke",
"network_address_detected": "${network}-Adresse erkannt",
"network_prefix_on": "auf",
"network_switch_failed": "Der Wechsel zum angeforderten Netzwerk ist fehlgeschlagen. Die Zahlung wurde nicht ausgeführt. Bitte versuchen Sie es erneut.",
@@ -798,6 +803,7 @@
"pairing_error": "Kopplungsfehler",
"pairingInvalidEvent": "Ungültiges Pairing-Ereignis",
"passphrase": "Passphrase (Optional)",
+ "passphrase_view_keys": "Passphrase",
"passphrase_entry": "Passphrase-Eingabe",
"passphrase_raw": "Passphrase",
"passphrase_view_keys": "Passphrase",
@@ -847,6 +853,7 @@
"please_choose_one": "Bitte wählen Sie eine Option",
"please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät angezeigt wird",
"please_make_selection": "Bitte treffen Sie unten eine Auswahl, um Ihre Wallet zu erstellen oder wiederherzustellen.",
+ "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
"please_reference_document": "Bitte beachten Sie die untenstehenden Dokumente für weitere Informationen.",
"please_select": "Bitte auswählen:",
"please_select_backup_file": "Bitte wählen Sie eine Sicherungsdatei aus und geben Sie das Sicherungskennwort ein.",
@@ -1573,9 +1580,9 @@
"zcash_card_enable_later": "Sie können diese Karte jederzeit später in den Einstellungen aktivieren",
"zcash_card_missing_funds": "Fehlendes Guthaben?",
"zcash_card_scan": "Scannen",
- "zcash_card_warning": "Schließen Sie die App nicht, bis der Vorgang abgeschlossen ist. Andernfalls muss der Vorgang von Grund auf neu gestartet werden.",
"zcash_network_upgrade": "Zcash-Netzwerk-Upgrade",
"zcash_network_upgrade_desc_1": "Das Zcash-Netzwerk wurde auf Ironwood aktualisiert. Damit Ihr Guthaben wieder ausgegeben werden kann, muss es migriert werden. Die Migration erfolgt automatisch und unter Wahrung Ihrer Privatsphäre. Sie beginnt, sobald Sie Ihre Zcash-Wallet öffnen.",
"zcash_network_upgrade_desc_2": "Nach Abschluss der Migration wird Ihr verfügbares Guthaben wieder wie gewohnt angezeigt und Sie können Zcash wieder normal senden und empfangen.",
- "zcash_network_upgrade_desc_3": "Bitte lassen Sie Ihre Zcash-Wallet vollständig synchronisieren und anschließend die App geöffnet, bis die Migration abgeschlossen ist. Dies kann je nach Guthaben einige Minuten dauern."
+ "zcash_network_upgrade_desc_3": "Bitte lassen Sie Ihre Zcash-Wallet vollständig synchronisieren und anschließend die App geöffnet, bis die Migration abgeschlossen ist. Dies kann je nach Guthaben einige Minuten dauern.",
+ "zcash_card_warning": "Schließen Sie die App nicht, bis der Vorgang abgeschlossen ist. Andernfalls muss der Vorgang von Grund auf neu gestartet werden."
}
### res/values/strings_en.arb
@@ -177,6 +177,9 @@
"camera_permission_is_required": "Camera permission is required. \nPlease enable it from app settings.",
"cancel": "Cancel",
"cannot_manage_accounts_during_sync": "You can't manage accounts while the wallet is still syncing. Please try again later.",
+ "cannot_remove_last_asset": "You can't remove this asset because it is your last.",
+ "cannot_verify": "Cannot Verify",
+ "cannot_verify_description": "This domain cannot be verified. Check the request carefully before approving.",
"card_address": "Address:",
"card_order_reset_desc": "Restore card order to default settings?",
"card_style": "Card style",
@@ -206,6 +209,7 @@
"change_username": "Change Username",
"change_wallet_alert_content": "Do you want to change current wallet to ${wallet_name}?",
"change_wallet_alert_title": "Change current wallet",
+ "charts": "Charts",
"check_receiver_forms": "Please, check receiver forms",
"choose_a_payment_method": "Choose a payment method",
"choose_a_provider": "Choose a provider",
@@ -507,6 +511,7 @@
"failed_authentication": "Failed authentication. ${state_error}",
"failed_to_load_payment_methods": "Failed to load payment methods",
"faq": "FAQ",
+ "favorite": "Favorite",
"favorite_token": "Favorite token",
"favorite_token_desc": "The favorite token's balance will show on the balance card.",
"features": "Features",
@@ -528,13 +533,15 @@
"fixed_pair_not_supported": "This fixed pair is not supported with the selected swap services",
"fixed_rate": "Fixed rate",
"fixed_rate_alert": "You will be able to enter receive amount when fixed rate mode is checked. Do you want to switch to fixed rate mode?",
+ "followed_assets": "Followed assets",
"forgot_password": "Forgot Password",
"freeze": "Freeze",
"frequently_asked_questions": "Frequently asked questions",
"from": "From",
"frozen": "Frozen",
"frozen_balance": "Frozen Balance",
"full_balance": "Full Balance",
+ "gains": "Gains",
"gallery": "Gallery",
"gas_exceeds_allowance": "Gas required by transaction exceeds allowance.",
"gas_price": "Gas price",
@@ -656,6 +663,7 @@
"long_press_hide_balance": "Long press card to hide balance",
"long_press_show_balance": "Long press card to show balance",
"long_press_to_copy": "Long press to copy",
+ "losses": "Losses",
"low_fee": "Low fee",
"low_fee_alert": "You currently are using a low network fee priority. This could cause long waits, different rates, or canceled trades. We recommend setting a higher fee for a better experience.",
"low_kyc": "Low KYC",
@@ -667,6 +675,7 @@
"manual_input": "Manual Input",
"mark_as_redeemed": "Mark As Redeemed",
"market_place": "Marketplace",
+ "marketcap": "Marketcap",
"mask": "Mask",
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max": "Max",
### res/values/strings_es.arb
@@ -205,6 +205,7 @@
"change_username": "Cambiar nombre de usuario",
"change_wallet_alert_content": "¿Quieres cambiar la cartera actual a ${wallet_name}?",
"change_wallet_alert_title": "Cambiar la billetera actual",
+ "charts": "Gráficos",
"check_receiver_forms": "Por favor, revisa los campos del destinatario.",
"choose_a_payment_method": "Elige un método de pago",
"choose_a_provider": "Elige un proveedor",
@@ -237,6 +238,8 @@
"color_theme": "Tema de color",
"coming_soon_tag": "Próximamente",
"commit_transaction_amount_fee": "Confirmar transacción\nCantidad: ${amount}\nComisión: ${fee}",
+ "compatible_services": "Servicios compatibles",
+ "compatible_services_desc": "Usa la herramienta Escanear para leer los siguientes tipos de códigos QR compatibles con Cake Wallet:",
"compatible_networks": "Redes compatibles",
"compatible_services": "Servicios compatibles",
"compatible_services_desc": "Usa la herramienta Escanear para leer los siguientes tipos de códigos QR compatibles con Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Este par fijo no es compatible con los servicios de swap seleccionados",
"fixed_rate": "Tipo de cambio fijo",
"fixed_rate_alert": "Podrás ingresar la cantidad a recibir cuando el modo de tipo de cambio fijo esté activado. ¿Quieres cambiar al modo de tipo de cambio fijo?",
+ "followed_assets": "Activos seguidos",
"forgot_password": "Olvidé mi contraseña",
"freeze": "Congelar",
"frequently_asked_questions": "Preguntas frecuentes",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Todos los modelos más recientes (GPT-5.3, Claude 4.6).\nSin suscripción, paga con cripto.",
"narrow": "Estrecho",
"network": "Red",
+ "networks": "Redes",
"network_address_detected": "${network} dirección detectada",
"network_prefix_on": "en",
"network_switch_failed": "No se pudo cambiar a la red solicitada. El pago no se aplicó, inténtelo nuevamente.",
### res/values/strings_fa.arb
@@ -205,6 +205,7 @@
"change_username": "تغییر نام کاربری",
"change_wallet_alert_content": "آیا میخواهید کیف پول فعلی را به ${wallet_name} تغییر دهید؟",
"change_wallet_alert_title": "تغییر کیف پول فعلی",
+ "charts": "نمودارها",
"check_receiver_forms": "لطفاً فرمهای گیرنده را بررسی کنید",
"choose_a_payment_method": "یک روش پرداخت را انتخاب کنید",
"choose_a_provider": "یک ارائهدهنده را انتخاب کنید",
@@ -237,6 +238,8 @@
"color_theme": "تم رنگ",
"coming_soon_tag": "بهزودی",
"commit_transaction_amount_fee": "ثبت تراکنش\nمقدار: ${amount}\nکارمزد: ${fee}",
+ "compatible_services": "سرویسهای سازگار",
+ "compatible_services_desc": "از ابزار اسکن برای خواندن انواع کدهای QR زیر که در Cake Wallet پشتیبانی میشوند استفاده کنید:",
"compatible_networks": "شبکه های سازگار",
"compatible_services": "سرویسهای سازگار",
"compatible_services_desc": "از ابزار اسکن برای خواندن انواع کدهای QR زیر که در Cake Wallet پشتیبانی میشوند استفاده کنید:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "این جفتارز ثابت توسط سرویسهای سواپ انتخابشده پشتیبانی نمیشود",
"fixed_rate": "نرخ ثابت",
"fixed_rate_alert": "وقتی حالت نرخ ثابت فعال باشد، میتوانید مقدار دریافتی را وارد کنید.\nآیا میخواهید به حالت نرخ ثابت تغییر دهید؟",
+ "followed_assets": "دارایی های دنبال شده",
"forgot_password": "رمز عبور را فراموش کردهاید",
"freeze": "فریز",
"frequently_asked_questions": "سوالات متداول",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "همه جدیدترین مدلها (GPT-5.3, Claude 4.6).\nبدون اشتراک، پرداخت با کریپتو.",
"narrow": "باریک",
"network": "شبکه",
+ "networks": "شبکهها",
"network_address_detected": "آدرس ${network} شناسایی شد",
"network_prefix_on": "در",
"network_switch_failed": "تغییر به شبکه درخواستی انجام نشد. پرداخت اعمال نشد، لطفاً دوباره امتحان کنید.",
### res/values/strings_fr.arb
@@ -205,6 +205,7 @@
"change_username": "Changer le nom d’utilisateur",
"change_wallet_alert_content": "Voulez-vous remplacer le portefeuille actuel par ${wallet_name} ?",
"change_wallet_alert_title": "Changer le portefeuille actuel",
+ "charts": "Graphiques",
"check_receiver_forms": "Veuillez vérifier les informations du destinataire",
"choose_a_payment_method": "Choisissez un mode de paiement",
"choose_a_provider": "Choisissez un fournisseur",
@@ -237,6 +238,8 @@
"color_theme": "Thème de couleur",
"coming_soon_tag": "Bientôt disponible",
"commit_transaction_amount_fee": "Confirmer la transaction\nMontant : ${amount}\nFrais : ${fee}",
+ "compatible_services": "Services compatibles",
+ "compatible_services_desc": "Utilisez l'outil Scan pour lire les types de codes QR suivants pris en charge par Cake Wallet :",
"compatible_networks": "Réseaux compatibles",
"compatible_services": "Services compatibles",
"compatible_services_desc": "Utilisez l'outil Scan pour lire les types de codes QR suivants pris en charge par Cake Wallet :",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge par les services d'échange sélectionnés",
"fixed_rate": "Taux fixe",
"fixed_rate_alert": "Vous pourrez saisir le montant à recevoir lorsque le mode taux fixe est activé. Souhaitez-vous passer en mode taux fixe ?",
+ "followed_assets": "Actifs suivis",
"forgot_password": "Mot de passe oublié",
"freeze": "Geler",
"frequently_asked_questions": "Foire aux questions",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Tous les modèles les plus récents (GPT-5.3, Claude 4.6).\nPas d'abonnement, payez en crypto.",
"narrow": "Étroit",
"network": "Réseau",
+ "networks": "Réseaux",
"network_address_detected": "Adresse ${network} détectée",
"network_prefix_on": "sur",
"network_switch_failed": "Échec du basculement vers le réseau demandé. Le paiement n'a pas été appliqué, veuillez réessayer.",
### res/values/strings_gn.arb
@@ -205,6 +205,7 @@
"change_username": "Emoambue puruhára réra",
"change_wallet_alert_content": "¿Reipotápa emoambue ko'ág̃a pe billetera hag̃ua ${wallet_name}?",
"change_wallet_alert_title": "Emoambue billetera ko'ágãgua",
+ "charts": "Gráfico-kuéra rehegua",
"check_receiver_forms": "Ikatúpa, ehecha umi formulario receptor rehegua.",
"choose_a_payment_method": "Eiporavo peteĩ jehepyme'ẽrã",
"choose_a_provider": "Eiporavo peteĩ me'ẽha",
@@ -237,6 +238,8 @@
"color_theme": "Téma sa'y",
"coming_soon_tag": "Og̃uahẽ pototáma",
"commit_transaction_amount_fee": "Emoneĩ ñembohasa\nMboheta: ${amount}\nHepyme’ẽ: ${fee}",
+ "compatible_services": "Servicio-kuéra Ojokupytýva",
+ "compatible_services_desc": "Eipuru pe Scan tembiporu emoñe'ẽ hag̃ua ko'ã QR código Cake Wallet oipytyvõva:",
"compatible_networks": "Redes Ojogueraháva",
"compatible_services": "Servicio-kuéra Ojokupytýva",
"compatible_services_desc": "Eipuru pe Scan tembiporu emoñe'ẽ hag̃ua ko'ã QR código Cake Wallet oipytyvõva:",
@@ -279,6 +282,7 @@
"contact_support": "Eñe’ẽ Support ndive",
"contacts": "Contactos",
"continue_text": "Eku'e",
+ "contract_address": "Contrato Kundaharape",
"continue_to_swap": "Esegíkena eintercambia",
"contract_address": "Contrato Kundaharape",
"contract_warning": "Ko contrato dirección ojehechakuaa hag̃ua ikatuha haʼe peteĩ ñembotavy térã mba’e vai. Eñangareko porãke.",
@@ -474,6 +478,7 @@
"etherscan_history": "Etherscan rembiasakue",
"event": "Tembiaporã",
"events": "Tembiaporã",
+ "evm_ecosystem_description": "Tekotevẽramo, emoambue pe red ha token reipuruséva ko jehepyme'ẽme g̃uarã.",
"evm_address_detected": "EVM Dirección Ojejuhúva",
"evm_ecosystem_description": "Tekotevẽramo, emoambue pe red ha token reipuruséva ko jehepyme'ẽme g̃uarã.",
"exchange": "Mbojopyru",
@@ -528,6 +533,7 @@
"fixed_pair_not_supported": "Ko mokõi par fijo ndojehepyme’ẽi umi swap rembiapo ojeiporavo va’ekuépe.",
"fixed_rate": "Tasa fija",
"fixed_rate_alert": "Ikatu hag̃aite emoinge hag̃ua pe monto rehechaukáva rehecha hag̃ua reiporavóramo pe modo tasa fija. ¿Reipota piko remoambue pe modo tasa fija-pe?",
+ "followed_assets": "Umi activo rapykuéri oúva",
"forgot_password": "Nderesarái ñe’ẽñemi",
"freeze": "Mbo’y",
"frequently_asked_questions": "Porandu ha mbohovái ojeporuvéva",
@@ -711,6 +717,7 @@
"nanogpt_subtitle": "Opaite umi modelo ipyahuvéva (GPT-5.3, Claude 4.6).\nNdaipóri suscripción, ehepyme’ẽ cripto rupive.",
"narrow": "Poʼi",
"network": "Red",
+ "networks": "Red-kuéra",
"network_address_detected": "${network} Dirección Ojejuhúva",
"network_prefix_on": "pe",
"network_switch_failed": "Ndojehechái oñembohasa hag̃ua red ojejerurévape. Pe pago ndojejapóikuri, eñeha’ã jey.",
@@ -1234,6 +1241,8 @@
"support_title_other_links": "Ambue juaju pytyvõ rehegua",
"supported": "Ojepytyvõva",
"swap": "Swap",
+ "supported": "Ojepytyvõva",
+ "swap": "Swap",
"swap_from_network": "Embohasa ${network} guive.",
"swap_from_network_description": "Emondo hag̃ua viru ${targetNetwork} red-pe, tekotevẽta rembohasa ne ${currentNetwork} viru. ¿Reprocedese piko?",
"swap_id": "ID ñemoambue rehegua",
@@ -1248,6 +1257,7 @@
"swipe_to_send": "Emoinge hag̃ua emondo hag̃ua.",
"switch_camera": "Ombohasa cámara",
"switch_input_currency": "Oñemoambue cripto ha fiat jeike apytépe",
+ "switch_wallet": "Emoambue Billetera",
"switch_to_x_wallet": "Embohasa ${network} Wallet-pe",
"switch_wallet": "Emoambue Billetera",
"switchToETHWallet": "Ikatúpa emoambue peteĩ billetera Ethereum-pe ha eha’ãjey.",
### res/values/strings_ha.arb
@@ -205,6 +205,7 @@
"change_username": "Canja sunan mai amfani",
"change_wallet_alert_content": "Kana so ka canza walat ɗin yanzu zuwa ${wallet_name}?",
"change_wallet_alert_title": "Canja walat na yanzu",
+ "charts": "Charts",
"check_receiver_forms": "Da fatan za a duba fom ɗin mai karɓa",
"choose_a_payment_method": "Zaɓi hanyar biyan kuɗi",
"choose_a_provider": "Zaɓi mai ba da sabis",
@@ -237,6 +238,8 @@
"color_theme": "Jigon launi",
"coming_soon_tag": "Zai zo nan ba da jimawa ba",
"commit_transaction_amount_fee": "Tabbatar da ma'amala\nAdadi: ${amount}\nKuɗi: ${fee}",
+ "compatible_services": "Sabis masu jituwa",
+ "compatible_services_desc": "Yi amfani da kayan aikin Scan don karanta nau'ikan QR code masu zuwa da Cake Wallet ke goyon baya:",
"compatible_networks": "Hanyoyin sadarwa masu jituwa",
"compatible_services": "Sabis masu jituwa",
"compatible_services_desc": "Yi amfani da kayan aikin Scan don karanta nau'ikan QR code masu zuwa da Cake Wallet ke goyon baya:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Ba a tallafa wa wannan tsayayyen nau'i ba tare da sabis ɗin musayar da aka zaɓa ba",
"fixed_rate": "Kafaffen farashi",
"fixed_rate_alert": "Za ka iya shigar da adadin da za a karɓa idan an zaɓi yanayin ƙayyadadden ƙima. Kana so ka canza zuwa yanayin ƙayyadadden ƙima?",
+ "followed_assets": "Kadarori masu biyo baya",
"forgot_password": "An manta kalmar wucewa",
"freeze": "Daskare",
"frequently_asked_questions": "Tambayoyin da ake yawan yi",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Duk sabbin samfura (GPT-5.3, Claude 4.6).\nBabu biyan kuɗin rajista, biya da crypto.",
"narrow": "Kunkuntar",
"network": "Hanyar sadarwa",
+ "networks": "Hanyoyin sadarwa",
"network_address_detected": "An gano adireshin ${network}",
"network_prefix_on": "kan",
"network_switch_failed": "An kasa canzawa zuwa cibiyar sadarwar da ake nema. Ba a yi amfani da kuɗin ba, da fatan za a sake gwadawa.",
### res/values/strings_hi.arb
@@ -205,6 +205,7 @@
"change_username": "यूज़रनेम बदलें",
"change_wallet_alert_content": "क्या आप वर्तमान वॉलेट को ${wallet_name} में बदलना चाहते हैं?",
"change_wallet_alert_title": "वर्तमान वॉलेट बदलें",
+ "charts": "चार्ट",
"check_receiver_forms": "कृपया, प्राप्तकर्ता के फ़ॉर्म जाँचें",
"choose_a_payment_method": "भुगतान विधि चुनें",
"choose_a_provider": "एक प्रदाता चुनें",
@@ -237,6 +238,8 @@
"color_theme": "रंग थीम",
"coming_soon_tag": "जल्द आ रहा है",
"commit_transaction_amount_fee": "लेन-देन कमिट करें\nराशि: ${amount}\nशुल्क: ${fee}",
+ "compatible_services": "संगत सेवाएँ",
+ "compatible_services_desc": "Cake Wallet पर समर्थित निम्न प्रकार के QR कोड पढ़ने के लिए स्कैन टूल का उपयोग करें:",
"compatible_networks": "संगत नेटवर्क",
"compatible_services": "संगत सेवाएँ",
"compatible_services_desc": "Cake Wallet पर समर्थित निम्न प्रकार के QR कोड पढ़ने के लिए स्कैन टूल का उपयोग करें:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "यह निश्चित जोड़ी चयनित स्वैप सेवाओं के साथ समर्थित नहीं है",
"fixed_rate": "स्थिर दर",
"fixed_rate_alert": "फिक्स्ड रेट मोड चुनने पर आप प्राप्त राशि दर्ज कर पाएंगे। क्या आप फिक्स्ड रेट मोड पर स्विच करना चाहते हैं?",
+ "followed_assets": "परिसंपत्तियों का अनुसरण किया गया",
"forgot_password": "पासवर्ड भूल गए",
"freeze": "फ्रीज़",
"frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "सभी नवीनतम मॉडल (GPT-5.3, Claude 4.6)।\nकोई सब्सक्रिप्शन नहीं, क्रिप्टो से भुगतान करें।",
"narrow": "संकीर्ण",
"network": "नेटवर्क",
+ "networks": "नेटवर्क",
"network_address_detected": "${network} पता मिला",
"network_prefix_on": "पर",
"network_switch_failed": "अनुरोधित नेटवर्क पर स्विच करने में विफल. भुगतान लागू नहीं हुआ, कृपया पुनः प्रयास करें।",
### res/values/strings_hr.arb
@@ -205,6 +205,7 @@
"change_username": "Promijeni korisničko ime",
"change_wallet_alert_content": "Želite li promijeniti trenutni novčanik na ${wallet_name}?",
"change_wallet_alert_title": "Promijeni trenutni novčanik",
+ "charts": "Karte",
"check_receiver_forms": "Molimo provjerite obrasce primatelja",
"choose_a_payment_method": "Odaberite način plaćanja",
"choose_a_provider": "Odaberite pružatelja usluga",
@@ -237,6 +238,8 @@
"color_theme": "Tema boja",
"coming_soon_tag": "Uskoro",
"commit_transaction_amount_fee": "Potvrdi transakciju\nIznos: ${amount}\nNaknada: ${fee}",
+ "compatible_services": "Kompatibilne usluge",
+ "compatible_services_desc": "Upotrijebite alat Skeniraj za čitanje sljedećih vrsta QR kodova podržanih u Cake Walletu:",
"compatible_networks": "Kompatibilne mreže",
"compatible_services": "Kompatibilne usluge",
"compatible_services_desc": "Upotrijebite alat Skeniraj za čitanje sljedećih vrsta QR kodova podržanih u Cake Walletu:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Ovaj fiksni par nije podržan s odabranim uslugama zamjene",
"fixed_rate": "Fiksni tečaj",
"fixed_rate_alert": "Moći ćete unijeti iznos za primanje kada je uključen način rada fiksne stope. Želite li se prebaciti na način rada fiksne stope?",
+ "followed_assets": "Praćena sredstva",
"forgot_password": "Zaboravili ste lozinku",
"freeze": "Zamrzni",
"frequently_asked_questions": "Često postavljana pitanja",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Svi najnoviji modeli (GPT-5.3, Claude 4.6).\nBez pretplate, plaćajte kriptom.",
"narrow": "Usko",
"network": "Mreža",
+ "networks": "Mreže",
"network_address_detected": "Otkrivena ${network} adresa",
"network_prefix_on": "na",
"network_switch_failed": "Nije uspjelo prebacivanje na traženu mrežu. Plaćanje nije primijenjeno, pokušajte ponovno.",
### res/values/strings_hy.arb
@@ -205,6 +205,7 @@
"change_username": "Փոխել օգտանունը",
"change_wallet_alert_content": "Ցանկանո՞ւմ եք փոխել ընթացիկ դրամապանակը՝ ${wallet_name}-ի:",
"change_wallet_alert_title": "Փոխել ընթացիկ դրամապանակը",
+ "charts": "Գծապատկերներ",
"check_receiver_forms": "Խնդրում ենք ստուգել ստացողի դաշտերը",
"choose_a_payment_method": "Ընտրեք վճարման եղանակը",
"choose_a_provider": "Ընտրեք մատակարար",
@@ -237,6 +238,8 @@
"color_theme": "Գունային թեմա",
"coming_soon_tag": "Շուտով",
"commit_transaction_amount_fee": "Հաստատել գործարքը\nԳումար՝ ${amount}\nՄիջնորդավճար՝ ${fee}",
+ "compatible_services": "Համատեղելի ծառայություններ",
+ "compatible_services_desc": "Օգտագործեք Սկանավորման գործիքը՝ Cake Wallet-ի կողմից աջակցվող QR կոդերի հետևյալ տեսակները կարդալու համար՝",
"compatible_networks": "Համատեղելի ցանցեր",
"compatible_services": "Համատեղելի ծառայություններ",
"compatible_services_desc": "Օգտագործեք Սկանավորման գործիքը՝ Cake Wallet-ի կողմից աջակցվող QR կոդերի հետևյալ տեսակները կարդալու համար՝",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Այս ֆիքսված զույգը չի աջակցվում ընտրված փոխանակման ծառայությունների կողմից",
"fixed_rate": "Ֆիքսված փոխարժեք",
"fixed_rate_alert": "Դուք կկարողանաք մուտքագրել ստացվող գումարը, երբ ֆիքսված փոխարժեքի ռեժիմը միացված է։ Ցանկանո՞ւմ եք անցնել ֆիքսված փոխարժեքի ռեժիմին։",
+ "followed_assets": "Հետևվող ակտիվներ",
"forgot_password": "Մոռացել եք գաղտնաբառը",
"freeze": "Սառեցնել",
"frequently_asked_questions": "Հաճախ տրվող հարցեր",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Բոլոր ամենանոր մոդելները (GPT-5.3, Claude 4.6).\nԱռանց բաժանորդագրության՝ վճարեք կրիպտոարժույթով։",
"narrow": "Նեղ",
"network": "Ցանց",
+ "networks": "Ցանցեր",
"network_address_detected": "Հայտնաբերվել է ${network} հասցե",
"network_prefix_on": "վրա",
"network_switch_failed": "Չհաջողվեց անցնել պահանջվող ցանցին: Վճարումը չի կիրառվել, խնդրում ենք կրկին փորձել:",
### res/values/strings_id.arb
@@ -205,6 +205,7 @@
"change_username": "Ubah Nama Pengguna",
"change_wallet_alert_content": "Apakah Anda ingin mengganti dompet saat ini menjadi ${wallet_name}?",
"change_wallet_alert_title": "Ubah dompet saat ini",
+ "charts": "Grafik",
"check_receiver_forms": "Silakan periksa formulir penerima",
"choose_a_payment_method": "Pilih metode pembayaran",
"choose_a_provider": "Pilih penyedia",
@@ -237,6 +238,8 @@
"color_theme": "Tema warna",
"coming_soon_tag": "Segera hadir",
"commit_transaction_amount_fee": "Konfirmasi transaksi\nJumlah: ${amount}\nBiaya: ${fee}",
+ "compatible_services": "Layanan yang Kompatibel",
+ "compatible_services_desc": "Gunakan alat Pindai untuk membaca jenis kode QR berikut yang didukung di Cake Wallet:",
"compatible_networks": "Jaringan yang Kompatibel",
"compatible_services": "Layanan yang Kompatibel",
"compatible_services_desc": "Gunakan alat Pindai untuk membaca jenis kode QR berikut yang didukung di Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Pasangan tetap ini tidak didukung oleh layanan swap yang dipilih",
"fixed_rate": "Kurs tetap",
"fixed_rate_alert": "Anda akan dapat memasukkan jumlah yang akan diterima saat mode kurs tetap dicentang. Apakah Anda ingin beralih ke mode kurs tetap?",
+ "followed_assets": "Aset yang diikuti",
"forgot_password": "Lupa Kata Sandi",
"freeze": "Bekukan",
"frequently_asked_questions": "Pertanyaan yang Sering Diajukan",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Semua model terbaru (GPT-5.3, Claude 4.6).\nTanpa langganan, bayar dengan kripto.",
"narrow": "Sempit",
"network": "Jaringan",
+ "networks": "Jaringan",
"network_address_detected": "Alamat ${network} terdeteksi",
"network_prefix_on": "pada",
"network_switch_failed": "Gagal beralih ke jaringan yang diminta. Pembayaran tidak diterapkan, silakan coba lagi.",
### res/values/strings_it.arb
@@ -205,6 +205,7 @@
"change_username": "Cambia nome utente",
"change_wallet_alert_content": "Vuoi cambiare il portafoglio attuale in ${wallet_name}?",
"change_wallet_alert_title": "Cambia portafoglio corrente",
+ "charts": "Grafici",
"check_receiver_forms": "Per favore, controlla i campi del destinatario",
"choose_a_payment_method": "Scegli un metodo di pagamento",
"choose_a_provider": "Scegli un provider",
@@ -237,6 +238,8 @@
"color_theme": "Tema colore",
"coming_soon_tag": "Prossimamente",
"commit_transaction_amount_fee": "Conferma transazione\nImporto: ${amount}\nCommissione: ${fee}",
+ "compatible_services": "Servizi compatibili",
+ "compatible_services_desc": "Usa lo strumento Scansione per leggere i seguenti tipi di codici QR supportati su Cake Wallet:",
"compatible_networks": "Reti compatibili",
"compatible_services": "Servizi compatibili",
"compatible_services_desc": "Usa lo strumento Scansione per leggere i seguenti tipi di codici QR supportati su Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Questa coppia fissa non è supportata dai servizi di swap selezionati",
"fixed_rate": "Tasso fisso",
"fixed_rate_alert": "Potrai inserire l'importo da ricevere quando è selezionata la modalità a tasso fisso. Vuoi passare alla modalità a tasso fisso?",
+ "followed_assets": "Risorse seguite",
"forgot_password": "Password dimenticata",
"freeze": "Blocca",
"frequently_asked_questions": "Domande frequenti",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Tutti i modelli più recenti (GPT-5.3, Claude 4.6).\nNessun abbonamento, paga con cripto.",
"narrow": "Stretto",
"network": "Rete",
+ "networks": "Reti",
"network_address_detected": "Indirizzo ${network} rilevato",
"network_prefix_on": "SU",
"network_switch_failed": "Impossibile passare alla rete richiesta. Il pagamento non è stato applicato, riprova.",
### res/values/strings_ja.arb
@@ -205,6 +205,7 @@
"change_username": "ユーザー名を変更",
"change_wallet_alert_content": "現在のウォレットを${wallet_name}に変更しますか?",
"change_wallet_alert_title": "現在のウォレットを変更",
+ "charts": "チャート",
"check_receiver_forms": "受取人情報の入力内容を確認してください",
"choose_a_payment_method": "支払い方法を選択",
"choose_a_provider": "プロバイダーを選択",
@@ -237,6 +238,8 @@
"color_theme": "カラーテーマ",
"coming_soon_tag": "近日公開",
"commit_transaction_amount_fee": "トランザクションを確定\n金額: ${amount}\n手数料: ${fee}",
+ "compatible_services": "対応サービス",
+ "compatible_services_desc": "スキャンツールを使用して、Cake Walletで対応している以下の種類のQRコードを読み取れます:",
"compatible_networks": "互換性のあるネットワーク",
"compatible_services": "対応サービス",
"compatible_services_desc": "スキャンツールを使用して、Cake Walletで対応している以下の種類のQRコードを読み取れます:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "この固定ペアは、選択したスワップサービスではサポートされていません",
"fixed_rate": "固定レート",
"fixed_rate_alert": "固定レートモードにチェックを入れると、受取額を入力できるようになります。固定レートモードに切り替えますか?",
+ "followed_assets": "フォローされているアセット",
"forgot_password": "パスワードをお忘れですか?",
"freeze": "凍結",
"frequently_asked_questions": "よくある質問",
@@ -710,6 +714,7 @@
"nanogpt_subtitle": "すべての最新モデル(GPT-5.3、Claude 4.6)。\nサブスクリプション不要、暗号資産で支払い。",
"narrow": "狭い",
"network": "ネットワーク",
+ "networks": "ネットワーク",
"network_address_detected": "${network}アドレスが検出されました",
"network_prefix_on": "の上",
"network_switch_failed": "要求されたネットワークへの切り替えに失敗しました。支払いは適用されませんでした。もう一度お試しください。",
### res/values/strings_ko.arb
@@ -205,6 +205,7 @@
"change_username": "사용자 이름 변경",
"change_wallet_alert_content": "현재 지갑을 ${wallet_name}(으)로 변경하시겠습니까?",
"change_wallet_alert_title": "현재 지갑 변경",
+ "charts": "차트",
"check_receiver_forms": "수신자 입력란을 확인해 주세요.",
"choose_a_payment_method": "결제 방법을 선택하세요",
"choose_a_provider": "제공업체 선택",
@@ -237,6 +238,8 @@
"color_theme": "색상 테마",
"coming_soon_tag": "출시 예정",
"commit_transaction_amount_fee": "트랜잭션 제출\n금액: ${amount}\n수수료: ${fee}",
+ "compatible_services": "호환 서비스",
+ "compatible_services_desc": "스캔 도구를 사용하여 Cake Wallet에서 지원하는 다음 유형의 QR 코드를 읽으세요:",
"compatible_networks": "호환 가능한 네트워크",
"compatible_services": "호환 서비스",
"compatible_services_desc": "스캔 도구를 사용하여 Cake Wallet에서 지원하는 다음 유형의 QR 코드를 읽으세요:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "이 고정 페어는 선택한 스왑 서비스에서 지원되지 않습니다",
"fixed_rate": "고정 환율",
"fixed_rate_alert": "고정 환율 모드를 선택하면 수령 금액을 입력할 수 있습니다. 고정 환율 모드로 전환하시겠습니까?",
+ "followed_assets": "팔로우하는 자산",
"forgot_password": "비밀번호를 잊으셨나요?",
"freeze": "동결",
"frequently_asked_questions": "자주 묻는 질문",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "최신 모델 모두(GPT-5.3, Claude 4.6).\n구독 없이 암호화폐로 결제.",
"narrow": "좁게",
"network": "네트워크",
+ "networks": "네트워크",
"network_address_detected": "${network} 주소가 감지되었습니다",
"network_prefix_on": "~에",
"network_switch_failed": "요청한 네트워크로 전환하지 못했습니다. 결제가 적용되지 않았습니다. 다시 시도해 주세요.",
### res/values/strings_my.arb
@@ -205,6 +205,7 @@
"change_username": "အသုံးပြုသူအမည် ပြောင်းရန်",
"change_wallet_alert_content": "လက်ရှိပိုက်ဆံအိတ်ကို ${wallet_name} သို့ ပြောင်းလိုပါသလား။",
"change_wallet_alert_title": "လက်ရှိ Wallet ကို ပြောင်းရန်",
+ "charts": "ဇယားများ",
"check_receiver_forms": "ကျေးဇူးပြု၍ လက်ခံသူဖောင်များကို စစ်ဆေးပါ။",
"choose_a_payment_method": "ငွေပေးချေမှုနည်းလမ်းကို ရွေးချယ်ပါ",
"choose_a_provider": "ဝန်ဆောင်မှုပေးသူကို ရွေးချယ်ပါ",
@@ -237,6 +238,8 @@
"color_theme": "အရောင်အပြင်အဆင်",
"coming_soon_tag": "မကြာမီလာမည်",
"commit_transaction_amount_fee": "ငွေလွှဲမှုကို အတည်ပြုပါ\nပမာဏ: ${amount}\nအခကြေးငွေ: ${fee}",
+ "compatible_services": "တွဲဖက်အသုံးပြုနိုင်သော ဝန်ဆောင်မှုများ",
+ "compatible_services_desc": "Cake Wallet တွင် ပံ့ပိုးထားသော အောက်ပါ QR ကုဒ်အမျိုးအစားများကို ဖတ်ရန် Scan tool ကို အသုံးပြုပါ:",
"compatible_networks": "လိုက်ဖက်သောကွန်ရက်များ",
"compatible_services": "တွဲဖက်အသုံးပြုနိုင်သော ဝန်ဆောင်မှုများ",
"compatible_services_desc": "Cake Wallet တွင် ပံ့ပိုးထားသော အောက်ပါ QR ကုဒ်အမျိုးအစားများကို ဖတ်ရန် Scan tool ကို အသုံးပြုပါ:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "ဤပုံသေစုံတွဲကို ရွေးချယ်ထားသော swap ဝန်ဆောင်မှုများဖြင့် မထောက်ပံ့ပါ",
"fixed_rate": "ပုံသေ နှုန်းထား",
"fixed_rate_alert": "ပုံသေနှုန်းထားမုဒ်ကို ရွေးထားသောအခါ လက်ခံရရှိမည့် ပမာဏကို ထည့်သွင်းနိုင်မည်ဖြစ်သည်။ ပုံသေနှုန်းထားမုဒ်သို့ ပြောင်းလိုပါသလား။",
+ "followed_assets": "လိုက်နာအပ်ပါတယ်။",
"forgot_password": "စကားဝှက်မေ့သွားပါသလား",
"freeze": "ရပ်ဆိုင်း",
"frequently_asked_questions": "မေးလေ့ရှိသော မေးခွန်းများ",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "နောက်ဆုံးပေါ်မော်ဒယ်များအားလုံး (GPT-5.3, Claude 4.6)။\nစာရင်းသွင်းမှုမလို၊ crypto ဖြင့်ပေးချေပါ။",
"narrow": "ကျဉ်း",
"network": "ကွန်ရက်",
+ "networks": "ကွန်ရက်များ",
"network_address_detected": "${network} လိပ်စာကို တွေ့ရှိသည်",
"network_prefix_on": "on",
"network_switch_failed": "တောင်းဆိုထားသောကွန်ရက်သို့ ပြောင်း၍မရပါ။ ငွေပေးချေမှုကို မသက်ရောက်ပါ၊ ထပ်စမ်းကြည့်ပါ။",
### res/values/strings_nl.arb
@@ -205,6 +205,7 @@
"change_username": "Verander de gebruikersnaam",
"change_wallet_alert_content": "Wil je de huidige wallet wijzigen in ${wallet_name}?",
"change_wallet_alert_title": "Wijzig huidige wallet",
+ "charts": "Grafieken",
"check_receiver_forms": "Controleer de formulieren van de ontvanger",
"choose_a_payment_method": "Kies een betaalmethode",
"choose_a_provider": "Kies een provider",
@@ -237,6 +238,8 @@
"color_theme": "Kleurthema",
"coming_soon_tag": "Binnenkort",
"commit_transaction_amount_fee": "Bevestig transactie\nBedrag: ${amount}\nKosten: ${fee}",
+ "compatible_services": "Compatibele services",
+ "compatible_services_desc": "Gebruik de Scan-tool om de volgende soorten QR-codes te lezen die door Cake Wallet worden ondersteund:",
"compatible_networks": "Compatibele netwerken",
"compatible_services": "Compatibele services",
"compatible_services_desc": "Gebruik de Scan-tool om de volgende soorten QR-codes te lezen die door Cake Wallet worden ondersteund:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Dit vaste paar wordt niet ondersteund met de geselecteerde swap-services",
"fixed_rate": "Vast tarief",
"fixed_rate_alert": "Je kunt het ontvangen bedrag invoeren wanneer de modus voor vaste tarieven is aangevinkt. Wil je overschakelen naar de vaste-tariefmodus?",
+ "followed_assets": "Gevolgde activa",
"forgot_password": "Wachtwoord vergeten",
"freeze": "Bevriezen",
"frequently_asked_questions": "Veelgestelde vragen",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Alle nieuwste modellen (GPT-5.3, Claude 4.6).\nGeen abonnement, betaal met crypto.",
"narrow": "Smal",
"network": "Netwerk",
+ "networks": "Netwerken",
"network_address_detected": "${network}-adres gedetecteerd",
"network_prefix_on": "op",
"network_switch_failed": "Kan niet overschakelen naar het gevraagde netwerk. De betaling is niet toegepast. Probeer het opnieuw.",
### res/values/strings_pl.arb
@@ -205,6 +205,7 @@
"change_username": "Zmień nazwę użytkownika",
"change_wallet_alert_content": "Czy chcesz zmienić bieżący portfel na ${wallet_name}?",
"change_wallet_alert_title": "Zmień bieżący portfel",
+ "charts": "Wykresy",
"check_receiver_forms": "Proszę sprawdzić formularze odbiorcy",
"choose_a_payment_method": "Wybierz metodę płatności",
"choose_a_provider": "Wybierz dostawcę",
@@ -237,6 +238,8 @@
"color_theme": "Motyw kolorystyczny",
"coming_soon_tag": "Wkrótce",
"commit_transaction_amount_fee": "Zatwierdź transakcję\nKwota: ${amount}\nOpłata: ${fee}",
+ "compatible_services": "Zgodne usługi",
+ "compatible_services_desc": "Użyj narzędzia Skanuj, aby odczytać następujące typy kodów QR obsługiwane w Cake Wallet:",
"compatible_networks": "Kompatybilne sieci",
"compatible_services": "Zgodne usługi",
"compatible_services_desc": "Użyj narzędzia Skanuj, aby odczytać następujące typy kodów QR obsługiwane w Cake Wallet:",
@@ -472,6 +475,7 @@
"etherscan_history": "Historia w Etherscan",
"event": "Zdarzenie",
"events": "Zdarzenia",
+ "evm_ecosystem_description": "W razie potrzeby zmień sieć i token, których chcesz użyć do tej płatności.",
"evm_address_detected": "Wykryto adres EVM",
"evm_ecosystem_description": "W razie potrzeby zmień sieć i token, których chcesz użyć do tej płatności.",
"exchange": "Wymiana",
@@ -526,6 +530,7 @@
"fixed_pair_not_supported": "Ta stała para nie jest obsługiwana przez wybrane usługi swap.",
"fixed_rate": "Stały kurs",
"fixed_rate_alert": "Będziesz mógł wprowadzić kwotę do otrzymania, gdy tryb stałego kursu jest zaznaczony. Czy chcesz przełączyć się na tryb stałego kursu?",
+ "followed_assets": "Obserwowane aktywa",
"forgot_password": "Nie pamiętasz hasła",
"freeze": "Zamroź",
"frequently_asked_questions": "Najczęściej zadawane pytania",
@@ -709,6 +714,7 @@
"nanogpt_subtitle": "Wszystkie najnowsze modele (GPT-5.3, Claude 4.6).\nBez subskrypcji, płać kryptowalutą.",
"narrow": "Wąski",
"network": "Sieć",
+ "networks": "Sieci",
"network_address_detected": "Wykryto adres ${network}",
"network_prefix_on": "NA",
"network_switch_failed": "Nie udało się przełączyć na żądaną sieć. Płatność nie została zastosowana. Spróbuj ponownie.",
### res/values/strings_pt.arb
@@ -205,6 +205,7 @@
"change_username": "Alterar nome de utilizador",
"change_wallet_alert_content": "Deseja alterar a carteira atual para ${wallet_name}?",
"change_wallet_alert_title": "Alterar a carteira atual",
+ "charts": "Gráficos",
"check_receiver_forms": "Por favor, verifique os campos do destinatário",
"choose_a_payment_method": "Escolha um método de pagamento",
"choose_a_provider": "Escolha um provedor",
@@ -237,6 +238,8 @@
"color_theme": "Tema de cores",
"coming_soon_tag": "Em breve",
"commit_transaction_amount_fee": "Confirmar transação\nValor: ${amount}\nTaxa: ${fee}",
+ "compatible_services": "Serviços compatíveis",
+ "compatible_services_desc": "Use a ferramenta de digitalização para ler os seguintes tipos de códigos QR suportados na Cake Wallet:",
"compatible_networks": "Redes Compatíveis",
"compatible_services": "Serviços compatíveis",
"compatible_services_desc": "Use a ferramenta de digitalização para ler os seguintes tipos de códigos QR suportados na Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Este par fixo não é compatível com os serviços de swap selecionados",
"fixed_rate": "Taxa fixa",
"fixed_rate_alert": "Você poderá inserir o valor a receber quando o modo de taxa fixa estiver selecionado. Deseja mudar para o modo de taxa fixa?",
+ "followed_assets": "Ativos seguidos",
"forgot_password": "Esqueci minha senha",
"freeze": "Congelar",
"frequently_asked_questions": "Perguntas frequentes",
@@ -710,6 +714,7 @@
"nanogpt_subtitle": "Todos os modelos mais recentes (GPT-5.3, Claude 4.6).\nSem assinatura, pague com cripto.",
"narrow": "Estreito",
"network": "Rede",
+ "networks": "Redes",
"network_address_detected": "Endereço ${network} detectado",
"network_prefix_on": "sobre",
"network_switch_failed": "Falha ao mudar para a rede solicitada. O pagamento não foi aplicado, tente novamente.",
### res/values/strings_ru.arb
@@ -205,6 +205,7 @@
"change_username": "Изменить имя пользователя",
"change_wallet_alert_content": "Вы хотите сменить текущий кошелёк на ${wallet_name}?",
"change_wallet_alert_title": "Сменить текущий кошелек",
+ "charts": "Графики",
"check_receiver_forms": "Пожалуйста, проверьте формы получателя",
"choose_a_payment_method": "Выберите способ оплаты",
"choose_a_provider": "Выберите провайдера",
@@ -237,6 +238,8 @@
"color_theme": "Цветовая тема",
"coming_soon_tag": "Скоро",
"commit_transaction_amount_fee": "Подтвердить транзакцию\nСумма: ${amount}\nКомиссия: ${fee}",
+ "compatible_services": "Совместимые сервисы",
+ "compatible_services_desc": "Используйте инструмент сканирования, чтобы считывать следующие типы QR-кодов, поддерживаемые в Cake Wallet:",
"compatible_networks": "Совместимые сети",
"compatible_services": "Совместимые сервисы",
"compatible_services_desc": "Используйте инструмент сканирования, чтобы считывать следующие типы QR-кодов, поддерживаемые в Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Эта фиксированная пара не поддерживается выбранными сервисами обмена",
"fixed_rate": "Фиксированный курс",
"fixed_rate_alert": "Вы сможете ввести сумму получения, когда будет включён режим фиксированного курса. Хотите переключиться в режим фиксированного курса?",
+ "followed_assets": "Отслеживаемые активы",
"forgot_password": "Забыли пароль?",
"freeze": "Заморозить",
"frequently_asked_questions": "Часто задаваемые вопросы",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Все новейшие модели (GPT-5.3, Claude 4.6).\nБез подписки, оплата криптовалютой.",
"narrow": "Узкий",
"network": "Сеть",
+ "networks": "Сети",
"network_address_detected": "Обнаружен адрес ${network}",
"network_prefix_on": "на",
"network_switch_failed": "Не удалось переключиться на запрошенную сеть. Платеж не был применен, попробуйте еще раз.",
### res/values/strings_th.arb
@@ -205,6 +205,7 @@
"change_username": "เปลี่ยนชื่อผู้ใช้",
"change_wallet_alert_content": "คุณต้องการเปลี่ยนกระเป๋าเงินปัจจุบันเป็น ${wallet_name} หรือไม่?",
"change_wallet_alert_title": "เปลี่ยนกระเป๋าเงินปัจจุบัน",
+ "charts": "แผนภูมิ",
"check_receiver_forms": "กรุณาตรวจสอบแบบฟอร์มของผู้รับ",
"choose_a_payment_method": "เลือกวิธีการชำระเงิน",
"choose_a_provider": "เลือกผู้ให้บริการ",
@@ -237,6 +238,8 @@
"color_theme": "ธีมสี",
"coming_soon_tag": "เร็ว ๆ นี้",
"commit_transaction_amount_fee": "ยืนยันธุรกรรม\nจำนวน: ${amount}\nค่าธรรมเนียม: ${fee}",
+ "compatible_services": "บริการที่รองรับ",
+ "compatible_services_desc": "ใช้เครื่องมือสแกนเพื่ออ่าน QR Code ประเภทต่อไปนี้ที่ Cake Wallet รองรับ:",
"compatible_networks": "เครือข่ายที่เข้ากันได้",
"compatible_services": "บริการที่รองรับ",
"compatible_services_desc": "ใช้เครื่องมือสแกนเพื่ออ่าน QR Code ประเภทต่อไปนี้ที่ Cake Wallet รองรับ:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "คู่แบบคงที่นี้ไม่รองรับกับบริการสวอปที่เลือก",
"fixed_rate": "อัตราคงที่",
"fixed_rate_alert": "คุณจะสามารถป้อนจำนวนเงินที่ต้องการรับได้เมื่อเปิดใช้งานโหมดอัตราคงที่ คุณต้องการสลับไปที่โหมดอัตราคงที่หรือไม่?",
+ "followed_assets": "ทรัพย์สินที่ตามมา",
"forgot_password": "ลืมรหัสผ่าน",
"freeze": "ระงับ",
"frequently_asked_questions": "คำถามที่พบบ่อย",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "โมเดลใหม่ล่าสุดทั้งหมด (GPT-5.3, Claude 4.6)\nไม่ต้องสมัครสมาชิก จ่ายด้วยคริปโต",
"narrow": "แคบ",
"network": "เครือข่าย",
+ "networks": "เครือข่าย",
"network_address_detected": "ตรวจพบที่อยู่ ${network}",
"network_prefix_on": "บน",
"network_switch_failed": "ไม่สามารถสลับไปยังเครือข่ายที่ร้องขอได้ การชำระเงินไม่ได้ใช้ โปรดลองอีกครั้ง",
### res/values/strings_tl.arb
@@ -205,6 +205,7 @@
"change_username": "Palitan ang Username",
"change_wallet_alert_content": "Gusto mo bang palitan ang kasalukuyang wallet ng ${wallet_name}?",
"change_wallet_alert_title": "Palitan ang kasalukuyang wallet",
+ "charts": "Mga tsart",
"check_receiver_forms": "Mangyaring suriin ang mga form ng tatanggap",
"choose_a_payment_method": "Pumili ng paraan ng pagbabayad",
"choose_a_provider": "Pumili ng provider",
@@ -237,6 +238,8 @@
"color_theme": "Tema ng kulay",
"coming_soon_tag": "Paparating na",
"commit_transaction_amount_fee": "I-commit ang transaksyon\nHalaga: ${amount}\nBayad: ${fee}",
+ "compatible_services": "Mga Katugmang Serbisyo",
+ "compatible_services_desc": "Gamitin ang Scan tool upang basahin ang mga sumusunod na uri ng QR code na sinusuportahan sa Cake Wallet:",
"compatible_networks": "Mga Katugmang Network",
"compatible_services": "Mga Katugmang Serbisyo",
"compatible_services_desc": "Gamitin ang Scan tool upang basahin ang mga sumusunod na uri ng QR code na sinusuportahan sa Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Hindi sinusuportahan ang nakapirming pares na ito sa mga napiling serbisyo ng swap",
"fixed_rate": "Nakapirming rate",
"fixed_rate_alert": "Makakapaglagay ka ng halagang matatanggap kapag naka-check ang fixed rate mode. Gusto mo bang lumipat sa fixed rate mode?",
+ "followed_assets": "Sinunod ang mga asset",
"forgot_password": "Nakalimutan ang Password",
"freeze": "I-freeze",
"frequently_asked_questions": "Mga madalas itanong",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Lahat ng pinakabagong modelo (GPT-5.3, Claude 4.6).\nWalang subscription, magbayad gamit ang crypto.",
"narrow": "Makitid",
"network": "Network",
+ "networks": "Mga Network",
"network_address_detected": "Natukoy ang ${network} address",
"network_prefix_on": "sa",
"network_switch_failed": "Nabigong lumipat sa hiniling na network. Hindi nailapat ang pagbabayad, pakisubukang muli.",
### res/values/strings_tr.arb
@@ -205,6 +205,7 @@
"change_username": "Kullanıcı Adını Değiştir",
"change_wallet_alert_content": "Mevcut cüzdanı ${wallet_name} ile değiştirmek istiyor musunuz?",
"change_wallet_alert_title": "Mevcut cüzdanı değiştir",
+ "charts": "Grafikler",
"check_receiver_forms": "Lütfen alıcı bilgilerini kontrol edin",
"choose_a_payment_method": "Bir ödeme yöntemi seçin",
"choose_a_provider": "Bir sağlayıcı seçin",
@@ -237,6 +238,8 @@
"color_theme": "Renk teması",
"coming_soon_tag": "Yakında",
"commit_transaction_amount_fee": "İşlemi onayla\nTutar: ${amount}\nÜcret: ${fee}",
+ "compatible_services": "Uyumlu Hizmetler",
+ "compatible_services_desc": "Cake Wallet'ta desteklenen aşağıdaki QR kodu türlerini okumak için Tarama aracını kullanın:",
"compatible_networks": "Uyumlu Ağlar",
"compatible_services": "Uyumlu Hizmetler",
"compatible_services_desc": "Cake Wallet'ta desteklenen aşağıdaki QR kodu türlerini okumak için Tarama aracını kullanın:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Bu sabit parite, seçilen takas hizmetleriyle desteklenmiyor",
"fixed_rate": "Sabit kur",
"fixed_rate_alert": "Sabit oran modu seçiliyken alınacak tutarı girebileceksin. Sabit oran moduna geçmek ister misin?",
+ "followed_assets": "Takip edilen varlıklar",
"forgot_password": "Parolamı Unuttum",
"freeze": "Dondur",
"frequently_asked_questions": "Sıkça sorulan sorular",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "En yeni modellerin tümü (GPT-5.3, Claude 4.6).\nAbonelik yok, kripto ile ödeyin.",
"narrow": "Dar",
"network": "Ağ",
+ "networks": "Ağlar",
"network_address_detected": "${network} adresi algılandı",
"network_prefix_on": "Açık",
"network_switch_failed": "İstenilen ağa geçiş yapılamadı. Ödeme uygulanmadı, lütfen tekrar deneyin.",
### res/values/strings_uk.arb
@@ -205,6 +205,7 @@
"change_username": "Змінити ім’я користувача",
"change_wallet_alert_content": "Ви хочете змінити поточний гаманець на ${wallet_name}?",
"change_wallet_alert_title": "Змінити поточний гаманець",
+ "charts": "Діаграми",
"check_receiver_forms": "Будь ласка, перевірте форми одержувача",
"choose_a_payment_method": "Виберіть спосіб оплати",
"choose_a_provider": "Виберіть провайдера",
@@ -237,6 +238,8 @@
"color_theme": "Колірна тема",
"coming_soon_tag": "Скоро",
"commit_transaction_amount_fee": "Підтвердити транзакцію\nСума: ${amount}\nКомісія: ${fee}",
+ "compatible_services": "Сумісні сервіси",
+ "compatible_services_desc": "Скористайтеся інструментом сканування, щоб зчитати такі типи QR-кодів, які підтримує Cake Wallet:",
"compatible_networks": "Сумісні мережі",
"compatible_services": "Сумісні сервіси",
"compatible_services_desc": "Скористайтеся інструментом сканування, щоб зчитати такі типи QR-кодів, які підтримує Cake Wallet:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "Ця фіксована пара не підтримується вибраними сервісами обміну",
"fixed_rate": "Фіксований курс",
"fixed_rate_alert": "Ви зможете ввести суму отримання, коли буде увімкнено режим фіксованого курсу. Хочете перейти в режим фіксованого курсу?",
+ "followed_assets": "Активи, за якими ви стежите",
"forgot_password": "Забули пароль",
"freeze": "Заморозити",
"frequently_asked_questions": "Поширені запитання",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "Усі найновіші моделі (GPT-5.3, Claude 4.6).\nБез підписки, оплата криптовалютою.",
"narrow": "Вузький",
"network": "Мережа",
+ "networks": "Мережі",
"network_address_detected": "Виявлено адресу ${network}",
"network_prefix_on": "на",
"network_switch_failed": "Не вдалося переключитися на потрібну мережу. Платіж не застосовано, спробуйте ще раз.",
### res/values/strings_ur.arb
@@ -205,6 +205,7 @@
"change_username": "صارف نام تبدیل کریں",
"change_wallet_alert_content": "کیا آپ موجودہ والیٹ کو ${wallet_name} میں تبدیل کرنا چاہتے ہیں؟",
"change_wallet_alert_title": "موجودہ والیٹ تبدیل کریں",
+ "charts": "چارٹس",
"check_receiver_forms": "براہِ کرم وصول کنندہ کے فارم چیک کریں",
"choose_a_payment_method": "ادائیگی کا طریقہ منتخب کریں",
"choose_a_provider": "ایک فراہم کنندہ منتخب کریں",
@@ -237,6 +238,8 @@
"color_theme": "رنگ تھیم",
"coming_soon_tag": "جلد آ رہا ہے",
"commit_transaction_amount_fee": "لین دین کی تصدیق کریں\nرقم: ${amount}\nفیس: ${fee}",
+ "compatible_services": "موافق سروسز",
+ "compatible_services_desc": "Cake Wallet پر معاون درج ذیل اقسام کے QR کوڈز پڑھنے کے لیے Scan ٹول استعمال کریں:",
"compatible_networks": "ہم آہنگ نیٹ ورکس",
"compatible_services": "موافق سروسز",
"compatible_services_desc": "Cake Wallet پر معاون درج ذیل اقسام کے QR کوڈز پڑھنے کے لیے Scan ٹول استعمال کریں:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "منتخب کردہ سواپ سروسز کے ساتھ یہ فکسڈ جوڑی سپورٹ نہیں کی جاتی",
"fixed_rate": "مقررہ شرح",
"fixed_rate_alert": "فکسڈ ریٹ موڈ منتخب ہونے پر آپ وصولی کی رقم درج کر سکیں گے۔ کیا آپ فکسڈ ریٹ موڈ پر سوئچ کرنا چاہتے ہیں؟",
+ "followed_assets": "اثاثوں کی پیروی کی۔",
"forgot_password": "پاس ورڈ بھول گئے",
"freeze": "منجمد کریں",
"frequently_asked_questions": "اکثر پوچھے جانے والے سوالات",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "تمام جدید ترین ماڈلز (GPT-5.3، Claude 4.6)۔\nکوئی سبسکرپشن نہیں، کرپٹو کے ساتھ ادائیگی کریں۔",
"narrow": "تنگ",
"network": "نیٹ ورک",
+ "networks": "نیٹ ورکس",
"network_address_detected": "${network} ایڈریس کا پتہ چلا",
"network_prefix_on": "پر",
"network_switch_failed": "درخواست کردہ نیٹ ورک پر سوئچ کرنے میں ناکام۔ ادائیگی لاگو نہیں ہوئی، براہ کرم دوبارہ کوشش کریں۔",
### res/values/strings_vi.arb
@@ -205,6 +205,7 @@
"change_username": "Thay đổi tên người dùng",
"change_wallet_alert_content": "Bạn có muốn chuyển ví hiện tại sang ${wallet_name} không?",
"change_wallet_alert_title": "Thay đổi ví hiện tại",
+ "charts": "Biểu đồ",
"check_receiver_forms": "Vui lòng kiểm tra thông tin người nhận",
"choose_a_payment_method": "Chọn phương thức thanh toán",
"choose_a_provider": "Chọn nhà cung cấp",
@@ -237,6 +238,8 @@
"color_theme": "Chủ đề màu",
"coming_soon_tag": "Sắp ra mắt",
"commit_transaction_amount_fee": "Xác nhận giao dịch\nSố tiền: ${amount}\nPhí: ${fee}",
+ "compatible_services": "Dịch vụ tương thích",
+ "compatible_services_desc": "Sử dụng công cụ Quét để đọc các loại mã QR sau được hỗ trợ trên Cake Wallet:",
"compatible_networks": "Mạng tương thích",
"compatible_services": "Dịch vụ tương thích",
"compatible_services_desc": "Sử dụng công cụ Quét để đọc các loại mã QR sau được hỗ trợ trên Cake Wallet:",
@@ -527,6 +530,7 @@
"fixed_pair_not_supported": "Cặp tỷ giá cố định này không được hỗ trợ với các dịch vụ hoán đổi đã chọn",
"fixed_rate": "Tỷ giá cố định",
"fixed_rate_alert": "Bạn sẽ có thể nhập số lượng nhận khi chế độ tỷ giá cố định được bật. Bạn có muốn chuyển sang chế độ tỷ giá cố định không?",
+ "followed_assets": "Nội dung được theo dõi",
"forgot_password": "Quên mật khẩu",
"freeze": "Đóng băng",
"frequently_asked_questions": "Các câu hỏi thường gặp",
@@ -710,6 +714,7 @@
"nanogpt_subtitle": "Tất cả các mô hình mới nhất (GPT-5.3, Claude 4.6).\nKhông cần đăng ký, thanh toán bằng crypto.",
"narrow": "Hẹp",
"network": "Mạng",
+ "networks": "Mạng",
"network_address_detected": "Đã phát hiện địa chỉ ${network}",
"network_prefix_on": "TRÊN",
"network_switch_failed": "Không thể chuyển sang mạng được yêu cầu. Thanh toán chưa được áp dụng, vui lòng thử lại.",
### res/values/strings_yo.arb
@@ -205,6 +205,7 @@
"change_username": "Yi Orúkọ Olùmúlò Padà",
"change_wallet_alert_content": "Ṣé o fẹ́ yí wálẹ́ẹ̀tì tó wà lọ́wọ́lọ́wọ́ padà sí ${wallet_name}?",
"change_wallet_alert_title": "Pààrọ̀ àpamọ́wọ́ lọ́wọ́lọ́wọ́",
+ "charts": "Awọn aworan atọka",
"check_receiver_forms": "Jọwọ, ṣayẹwo àwọn fọ́ọ̀mù olùgbà",
"choose_a_payment_method": "Yan ọna ìsanwó kan",
"choose_a_provider": "Yan olupese kan",
@@ -237,6 +238,8 @@
"color_theme": "Àkòrí àwọ̀",
"coming_soon_tag": "N bọ laipẹ",
"commit_transaction_amount_fee": "Fọwọ́sí ìdúnàdúrà\nIye: ${amount}\nOwó ìdúnàdúrà: ${fee}",
+ "compatible_services": "Àwọn Iṣẹ́ Tó Bámu",
+ "compatible_services_desc": "Lo irinṣẹ́ Scan láti ka àwọn irú kóòdù QR wọ̀nyí tí a ṣe àtìlẹ́yìn fún lórí Cake Wallet:",
"compatible_networks": "Awọn nẹtiwọki ibaramu",
"compatible_services": "Àwọn Iṣẹ́ Tó Bámu",
"compatible_services_desc": "Lo irinṣẹ́ Scan láti ka àwọn irú kóòdù QR wọ̀nyí tí a ṣe àtìlẹ́yìn fún lórí Cake Wallet:",
@@ -527,6 +530,7 @@
"fixed_pair_not_supported": "Akọsopọ paṣipaarọ ti o wa titi yii ko ni atilẹyin pẹlu awọn iṣẹ swap ti o yan",
"fixed_rate": "Oṣuwọn tí ó dúró ṣinṣin",
"fixed_rate_alert": "Ẹ ó lè tẹ iye tí ẹ fẹ́ gba wọlé nígbà tí a bá ti yan ipo oṣuwọn títọ́. Ṣé ẹ fẹ́ yí padà sí ipo oṣuwọn títọ́?",
+ "followed_assets": "Awọn ohun-ini atẹle",
"forgot_password": "Gbagbe Ọ̀rọ̀ aṣínà",
"freeze": "Dì",
"frequently_asked_questions": "Àwọn ìbéèrè tí a máa ń béèrè lọ́pọ̀ ìgbà",
@@ -710,6 +714,7 @@
"nanogpt_subtitle": "Gbogbo àwọn àwòṣe tuntun jùlọ (GPT-5.3, Claude 4.6).\nKò sí ìforúkọsílẹ̀, sanwó pẹ̀lú crypto.",
"narrow": "Dín",
"network": "Nẹ́tíwọ́ọ̀kì",
+ "networks": "Àwọn Nẹ́tíwọ́ọ̀kì",
"network_address_detected": "A ti rí àdírẹ́sì ${network}",
"network_prefix_on": "lori",
"network_switch_failed": "Kuna lati yipada si nẹtiwọki ti o beere. Owo sisan ko lo, jọwọ gbiyanju lẹẹkansi.",
### res/values/strings_zh.arb
@@ -205,6 +205,7 @@
"change_username": "更改用户名",
"change_wallet_alert_content": "是否要将当前钱包切换为${wallet_name}?",
"change_wallet_alert_title": "更改当前钱包",
+ "charts": "图表",
"check_receiver_forms": "请检查收款信息表单",
"choose_a_payment_method": "选择支付方式",
"choose_a_provider": "选择服务提供商",
@@ -237,6 +238,8 @@
"color_theme": "颜色主题",
"coming_soon_tag": "即将推出",
"commit_transaction_amount_fee": "提交交易\n金额:${amount}\n手续费:${fee}",
+ "compatible_services": "兼容的服务",
+ "compatible_services_desc": "使用扫描工具读取 Cake Wallet 支持的以下类型二维码:",
"compatible_networks": "兼容网络",
"compatible_services": "兼容的服务",
"compatible_services_desc": "使用扫描工具读取 Cake Wallet 支持的以下类型二维码:",
@@ -526,6 +529,7 @@
"fixed_pair_not_supported": "所选的兑换服务不支持该固定交易对",
"fixed_rate": "固定汇率",
"fixed_rate_alert": "勾选固定汇率模式后,您将可以输入接收金额。您要切换到固定汇率模式吗?",
+ "followed_assets": "关注资产",
"forgot_password": "忘记密码",
"freeze": "冻结",
"frequently_asked_questions": "常见问题",
@@ -709,6 +713,7 @@
"nanogpt_subtitle": "所有最新模型(GPT-5.3、Claude 4.6)。\n无需订阅,用加密货币支付。",
"narrow": "窄",
"network": "网络",
+ "networks": "网络",
"network_address_detected": "检测到 ${network} 地址",
"network_prefix_on": "在",
"network_switch_failed": "无法切换到请求的网络。付款未成功,请重试。",Why this scored 22/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.