remove old ui (#3629)

* remove old ui

* remove imports to nonexistent files

* remove old buy/sell

* remove unreachable pages

* readd Routes.exchange

* remove broken di
This commit is contained in:
malik1004x
2026-09-22 16:46:42 +02:00
committed by GitHub
parent c96359321f
commit d38c748127
155 changed files with 74 additions and 18935 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-5
View File
@@ -3,8 +3,6 @@ import 'package:cw_core/enumerate.dart';
class ReceivePageOption implements Enumerate {
static const mainnet = ReceivePageOption._('mainnet');
static const testnet = ReceivePageOption._('testnet');
static const anonPayInvoice = ReceivePageOption._('anonPayInvoice');
static const anonPayDonationLink = ReceivePageOption._('anonPayDonationLink');
const ReceivePageOption._(this.value,
{this.iconPath, this.description, this.isCommon = false, this.addAddressWord = false});
@@ -22,7 +20,4 @@ class ReceivePageOption implements Enumerate {
const ReceivePageOptions = [
ReceivePageOption.mainnet,
// FIXME anonpay disabed for new ui beta. restore after screens are ready
// ReceivePageOption.anonPayInvoice,
// ReceivePageOption.anonPayDonationLink
];
@@ -90,7 +90,6 @@ class CakePayBuyCardPage extends BasePage {
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: titleColor(context)),
);
}
@@ -168,12 +168,10 @@ class _CakePayCardsPageBodyState extends State<CakePayCardsPageBody> {
child: TabViewWrapper(
labelStyle: TextStyle(
color: titleColor,
fontFamily: 'Lato',
fontSize: 20,
fontWeight: FontWeight.w600),
unselectedLabelStyle: TextStyle(
color: titleColor?.withAlpha(150) ?? Colors.white70,
fontFamily: 'Lato',
fontSize: 20,
fontWeight: FontWeight.w400),
indicatorColor: titleColor,
@@ -172,7 +172,6 @@ class _ThreeCheckboxAlertContentState extends State<ThreeCheckboxAlertContent> {
S.of(context).settings_terms_and_conditions,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Colors.blueAccent,
decoration: TextDecoration.none,
@@ -190,7 +189,6 @@ class _ThreeCheckboxAlertContentState extends State<ThreeCheckboxAlertContent> {
style: TextStyle(
color: Colors.red,
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
decoration: TextDecoration.none,
),
@@ -1,280 +0,0 @@
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_type.dart';
import 'dart:async';
part 'yat_store.g.dart';
class YatLink {
static const partnerId = ''; // 'CW';
static const baseDevUrl = ''; // 'https://yat.fyi';
static const baseReleaseUrl = ''; // 'https://y.at';
static const signInSuffix = ''; // '/partner/$partnerId/link-email';
static const createSuffix = ''; // '/create';
static const managePath = ''; // '/partner/$partnerId/manage';
static const queryParameter = ''; // '?address_json=';
static const apiDevUrl = ''; // 'https://a.yat.fyi';
static const apiReleaseUrl = ''; // 'https://a.y.at';
static const requestDevUrl = ''; // 'https://a.yat.fyi/emoji_id/';
static const requestReleaseUrl = ''; //'https://a.y.at/emoji_id/';
static const startFlowUrl = ''; // 'https://www.y03btrk.com/4RQSJ/6JHXF/';
static const isDevMode = true;
static const tags = <String, List<String>>{
"XMR": ['0x1001', '0x1002'],
"BTC": ['0x1003'],
"LTC": ['0x1019']
};
static String get apiUrl => YatLink.isDevMode ? YatLink.apiDevUrl : YatLink.apiReleaseUrl;
static String get emojiIdUrl => apiUrl + '/emoji_id/';
static String get baseUrl => YatLink.isDevMode ? YatLink.baseDevUrl : YatLink.baseReleaseUrl;
}
Future<List<String>> fetchYatAddress(String emojiId, String ticker) async {
throw Exception();
//final url = YatLink.emojiIdUrl + emojiId + '/payment';
//final response = await get(url);
//if (response.statusCode != 200) {
// throw YatException(text: response.body.toString());
//}
//final addresses = <String>[];
//final currency = ticker.toUpperCase();
//final responseJSON = json.decode(response.body) as Map<String, dynamic>;
//final result = responseJSON['result'] as Map<dynamic, dynamic>;
//result.forEach((dynamic key, dynamic value) {
// final tag = key as String ?? '';
// final record = value as Map<String, dynamic>;
// if (YatLink.tags[currency]?.contains(tag) ?? false) {
// final address = record['address'] as String;
// if (address?.isNotEmpty ?? false) {
// addresses.add(address);
// }
// }
//});
//return addresses;
return [];
}
Future<String> fetchYatAccessToken(String refreshToken) async {
throw Exception();
//try {
// final url = YatLink.apiUrl + '/auth/token/refresh';
// final bodyJson = json.encode({'refresh_token': refreshToken});
// final response = await post(
// url,
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Accept': '*/*'
// },
// body: bodyJson);
// if (response.statusCode != 200) {
// throw YatException(text: response.body.toString());
// }
// final responseJSON = json.decode(response.body) as Map<String, dynamic>;
// return responseJSON['access_token'] as String;
//}catch(_) {
// return '';
//}
return '';
}
Future<String> fetchYatApiKey(String accessKey) async {
throw Exception();
//try {
// final url = YatLink.apiUrl + '/api_keys';
// final bodyJson = json.encode({'name': 'CW'});
// final response = await post(
// url,
// headers: <String, String>{
// 'Authorization': 'Bearer $accessKey',
// 'Content-Type': 'application/json',
// 'Accept': '*/*'
// },
// body: bodyJson);
// if (response.statusCode != 200) {
// throw YatException(text: response.body.toString());
// }
// final responseJSON = json.decode(response.body) as Map<String, dynamic>;
// return responseJSON['api_key'] as String;
//}catch(_) {
// return '';
//}
return '';
}
Future<void> updateEmojiIdAddress(
String emojiId, String address, String apiKey, WalletType type) async {
throw Exception();
//final url = YatLink.emojiIdUrl + emojiId;
//final cur = walletTypeToCryptoCurrency(type);
//final curFormatted = cur.toString().toUpperCase();
//var tag = '';
//if (type == WalletType.monero && !address.startsWith('4')) {
// tag = YatLink.tags[curFormatted].last;
//} else {
// tag = YatLink.tags[curFormatted].first;
//}
//final bodyJson = json.encode({
// 'insert': [{
// 'data': address,
// 'tag': tag
// }]
//});
//final response = await patch(
// url,
// headers: <String, String>{
// 'x-api-key': apiKey,
// 'Content-Type': 'application/json',
// 'Accept': '*/*'
// },
// body: bodyJson);
//if (response.statusCode != 200) {
// throw YatException(text: response.body.toString());
//}
}
Future<String> visualisationForEmojiId(String emojiId) async {
throw Exception();
//final url = YatLink.emojiIdUrl + emojiId + '/json/VisualizerFileLocations';
//final response = await get(url);
//final responseJSON = json.decode(response.body) as Map<String, dynamic>;
//final data = responseJSON['data'] as Map<String, dynamic>;
//final result = data['gif'] as String ?? '';
//return result;
return '';
}
class YatStore = YatStoreBase with _$YatStore;
abstract class YatStoreBase with Store {
YatStoreBase({required this.appStore, required this.secureStorage})
: _wallet = appStore.wallet,
emoji = appStore.wallet?.walletInfo?.yatEmojiId ?? '',
refreshToken = '',
accessToken = '',
apiKey = '',
emojiIncommingSC = StreamController<String>.broadcast() {
//reaction((_) => appStore.wallet, _onWalletChange);
//reaction((_) => emoji, (String _) => _onEmojiChange());
//reaction((_) => refreshToken, (String _) => _onRefreshTokenChange());
}
static const yatRefreshTokenKeyBase = 'yat_refresh_token';
static const yatAccessTokenKeyBase = 'yat_access_token';
static const yatApiKeyBase = 'yat_api_key';
static String yatRefreshTokenKey(String name) => '${yatRefreshTokenKeyBase}_$name';
static String yatAccessTokenKey(String name) => '${yatAccessTokenKeyBase}_$name';
static String yatApiKey(String name) => '${yatApiKeyBase}_$name';
AppStore appStore;
SecureStorage secureStorage;
@observable
String emoji;
@observable
String refreshToken;
@observable
String accessToken;
@observable
String apiKey;
StreamController<String> emojiIncommingSC;
Stream<String> get emojiIncommingStream => emojiIncommingSC.stream;
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? _wallet;
Future<void> init() async {
if (_wallet == null) {
return;
}
refreshToken =
await secureStorage.read(key: yatRefreshTokenKey(_wallet!.walletInfo.name)) ?? '';
accessToken = await secureStorage.read(key: yatAccessTokenKey(_wallet!.walletInfo.name)) ?? '';
apiKey = await secureStorage.read(key: yatApiKey(_wallet!.walletInfo.name)) ?? '';
}
@action
void _onWalletChange(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet) {
this._wallet = wallet;
emoji = wallet?.walletInfo?.yatEmojiId ?? '';
init();
}
@action
void _onEmojiChange() {
try {
final walletInfo = _wallet?.walletInfo;
if (walletInfo == null) {
return;
}
walletInfo!.yatEid = emoji;
walletInfo!.save();
} catch (e) {
printV(e.toString());
}
}
@action
Future<void> _onRefreshTokenChange() async {
throw Exception();
//try {
// await secureStorage.write(key: yatRefreshTokenKey(_wallet.walletInfo.name), value: refreshToken);
// accessToken = await fetchYatAccessToken(refreshToken);
// await secureStorage.write(key: yatAccessTokenKey(_wallet.walletInfo.name), value: accessToken);
// apiKey = await fetchYatApiKey(accessToken);
// await secureStorage.write(key: yatApiKey(_wallet.walletInfo.name), value: accessToken);
//} catch (e) {
// printV(e.toString());
//}
}
String defineQueryParameters() {
throw Exception();
//final result = <String, String>{};
//final tags = YatLink.tags[_wallet.currency.toString().toUpperCase()];
//String tag = tags.first;
//if (_wallet.type == WalletType.monero
// && _wallet.walletAddresses.address.startsWith('4')) {
// tag = tags.last;
//}
//result[tag] = '${_wallet.walletAddresses.address}|${_wallet.name}';
//final addressJson = json.encode([result]);
//final addressJsonBytes = utf8.encode(addressJson);
//return base64.encode(addressJsonBytes);
return '';
}
}
+7 -241
View File
@@ -9,7 +9,6 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/address_resolver/address_resolver_service.dart';
import 'package:cake_wallet/core/anypay/anypay_service.dart';
import 'package:cake_wallet/core/address_resolver/yat/yat_service.dart';
import 'package:cake_wallet/core/address_resolver/yat/yat_store.dart';
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
@@ -83,22 +82,11 @@ import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dar
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
@@ -109,29 +97,15 @@ import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/socket_health_logs_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
import 'package:cake_wallet/src/screens/faq/faq_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
import 'package:cake_wallet/src/screens/payjoin_details/payjoin_details_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
@@ -139,12 +113,8 @@ import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@@ -156,7 +126,6 @@ import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_logs_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
@@ -164,7 +133,6 @@ import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
import 'package:cake_wallet/src/screens/start_tor/start_tor_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/support/support_page.dart';
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
@@ -172,7 +140,6 @@ import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
import 'package:cake_wallet/new-ui/pages/bridge/bridge_detail_page.dart';
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
@@ -200,8 +167,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
import 'package:cake_wallet/store/seed_settings_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/store/templates/send_template_store.dart';
import 'package:cake_wallet/store/wallet_list_store.dart';
import 'package:cake_wallet/themes/core/theme_store.dart';
import 'package:cake_wallet/tron/tron.dart';
@@ -209,7 +174,6 @@ import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/animated_ur_model.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
import 'package:cake_wallet/view_model/auth_view_model.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
@@ -217,7 +181,6 @@ import 'package:cake_wallet/view_model/bridge/bridge_details_view_model.dart';
import 'package:cake_wallet/view_model/bridge/bridge_history_view_model.dart';
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
import 'package:cake_wallet/view_model/cake_pay/cake_pay_account_view_model.dart';
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
@@ -225,9 +188,7 @@ import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.d
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
import "package:cake_wallet/view_model/dashboard/nft_send_view_model.dart";
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
@@ -263,7 +224,6 @@ import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:cake_wallet/view_model/settings/connection_sync_view_model.dart';
@@ -325,7 +285,6 @@ import 'buy/meld/meld_buy_provider.dart';
import 'dogecoin/dogecoin.dart';
import 'new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
import 'new-ui/widgets/addresses_page/address_info.dart';
import 'src/screens/buy/buy_sell_page.dart';
final getIt = GetIt.instance;
@@ -403,11 +362,6 @@ Future<void> setup({
getIt.registerSingleton<OrderFilterStore>(OrderFilterStore());
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore(getIt.get<AppStore>()));
getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
getIt.registerSingleton<SendTemplateStore>(SendTemplateStore(templateSource: _templates));
getIt.registerSingleton<ExchangeTemplateStore>(
ExchangeTemplateStore(templateSource: _exchangeTemplates));
getIt.registerSingleton<YatStore>(
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())..init());
getIt.registerSingleton<AnonpayTransactionsStore>(
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
getIt.registerSingleton<SeedSettingsStore>(SeedSettingsStore());
@@ -546,7 +500,6 @@ Future<void> setup({
getIt.registerFactory<WalletAddressListViewModel>(() => WalletAddressListViewModel(
appStore: getIt.get<AppStore>(),
yatStore: getIt.get<YatStore>(),
fiatConversionStore: getIt.get<FiatConversionStore>()));
getIt.registerFactory(() => BalanceViewModel(
@@ -557,7 +510,6 @@ Future<void> setup({
getIt.registerFactory(
() => ExchangeViewModel(
getIt.get<AppStore>(),
getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>(),
getIt.get<SharedPreferences>(),
getIt.get<ContactListViewModel>(),
@@ -585,7 +537,6 @@ Future<void> setup({
orderFilterStore: getIt.get<OrderFilterStore>(),
transactionFilterStore: getIt.get<TransactionFilterStore>(),
settingsStore: settingsStore,
yatStore: getIt.get<YatStore>(),
anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
payjoinTransactionsStore: getIt.get<PayjoinTransactionsStore>(),
sharedPreferences: getIt.get<SharedPreferences>(),
@@ -775,17 +726,6 @@ Future<void> setup({
return walletKitService;
});
getIt.registerFactory(() => BalancePage(
nftViewModel: getIt.get<NFTViewModel>(),
dashboardViewModel: getIt.get<DashboardViewModel>(),
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactory<DashboardPage>(() => DashboardPage(
bottomSheetService: getIt.get<BottomSheetService>(),
balancePage: getIt.get<BalancePage>(),
dashboardViewModel: getIt.get<DashboardViewModel>(),
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
));
getIt.registerFactory<NewDashboard>(() => NewDashboard(
dashboardViewModel: getIt.get<DashboardViewModel>(),
@@ -797,27 +737,6 @@ Future<void> setup({
nftViewModel: getIt.get<NFTViewModel>(),
));
getIt.registerFactory<DesktopSidebarWrapper>(() {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
return DesktopSidebarWrapper(
bottomSheetService: getIt.get<BottomSheetService>(),
dashboardViewModel: getIt.get<DashboardViewModel>(),
desktopSidebarViewModel: getIt.get<DesktopSidebarViewModel>(),
child: getIt.get<DesktopDashboardPage>(param1: _navigatorKey),
desktopNavigatorKey: _navigatorKey,
);
});
getIt.registerFactoryParam<DesktopDashboardPage, GlobalKey<NavigatorState>, void>(
(desktopKey, _) => DesktopDashboardPage(
balancePage: getIt.get<BalancePage>(),
dashboardViewModel: getIt.get<DashboardViewModel>(),
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
desktopKey: desktopKey,
));
getIt.registerFactory<TransactionsPage>(
() => TransactionsPage(dashboardViewModel: getIt.get<DashboardViewModel>()));
getIt.registerFactory<Setup2FAInfoPage>(() => Setup2FAInfoPage());
getIt.registerFactory<Setup2FAPage>(
@@ -829,43 +748,9 @@ Future<void> setup({
getIt.registerFactory<Modify2FAPage>(
() => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
getIt.registerFactory<DesktopSettingsPage>(
() => DesktopSettingsPage(getIt.get<DashboardViewModel>()));
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
getIt.registerFactoryParam<AnonInvoicePageViewModel, List<dynamic>, void>((args, _) {
final address = args.first as String;
final pageOption = args.last as ReceivePageOption;
return AnonInvoicePageViewModel(
getIt.get<AnonPayApi>(),
address,
getIt.get<SettingsStore>(),
getIt.get<AppStore>().wallet!,
_anonpayInvoiceInfoSource,
getIt.get<SharedPreferences>(),
pageOption,
);
});
getIt.registerFactoryParam<AnonPayInvoicePage, List<dynamic>, void>((List<dynamic> args, _) {
final pageOption = args.last as ReceivePageOption;
return AnonPayInvoicePage(getIt.get<AnonInvoicePageViewModel>(param1: args),
getIt.get<ReceiveOptionViewModel>(param1: pageOption));
});
getIt.registerFactory<ReceivePage>(
() => ReceivePage(addressListViewModel: getIt.get<WalletAddressListViewModel>()));
getIt.registerFactory<AddressPage>(
() => AddressPage(
addressListViewModel: getIt<WalletAddressListViewModel>(),
dashboardViewModel: getIt<DashboardViewModel>(),
receiveOptionViewModel: getIt<ReceiveOptionViewModel>(),
),
);
getIt.registerFactoryParam<NewReceivePage, bool?, CryptoCurrency?>((param1, param2) =>
NewReceivePage(
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
@@ -878,10 +763,6 @@ Future<void> setup({
(WalletAddressListItem? item, _) =>
WalletAddressEditOrCreateViewModel(wallet: getIt.get<AppStore>().wallet!, item: item));
getIt.registerFactoryParam<AddressEditOrCreatePage, dynamic, void>((dynamic item, _) =>
AddressEditOrCreatePage(
addressEditOrCreateViewModel:
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
getIt.registerFactoryParam<AddressLabelInputPopup, dynamic, void>((dynamic item, _) =>
AddressLabelInputPopup(
@@ -897,16 +778,10 @@ Future<void> setup({
walletAddressEditOrCreateViewModel:
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
getIt.registerFactory<SendTemplateViewModel>(() => SendTemplateViewModel(
getIt.get<AppStore>().wallet!,
getIt.get<AppStore>(),
getIt.get<SendTemplateStore>(),
getIt.get<FiatConversionStore>()));
getIt.registerFactoryParam<SendViewModel, UnspentCoinType?, void>(
(coinTypeToSpendFrom, _) => SendViewModel(
getIt.get<AppStore>(),
getIt.get<SendTemplateViewModel>(),
getIt.get<FiatConversionStore>(),
getIt.get<AddressResolverService>(),
getIt.get<BalanceViewModel>(),
@@ -921,14 +796,7 @@ Future<void> setup({
getIt.get<FeesViewModel>()),
);
getIt.registerFactoryParam<SendPage, PaymentRequest?, UnspentCoinType?>(
(PaymentRequest? initialPaymentRequest, coinTypeToSpendFrom) => SendPage(
sendViewModel: getIt.get<SendViewModel>(param1: coinTypeToSpendFrom),
authService: getIt.get<AuthService>(),
initialPaymentRequest: initialPaymentRequest,
paymentViewModel: getIt.get<PaymentViewModel>(),
walletSwitcherViewModel: getIt.get<WalletSwitcherViewModel>(),
));
getIt.registerFactoryParam<NewSendPage, SendPageParams?, void>((params, _) {
params ??= SendPageParams();
@@ -943,8 +811,7 @@ Future<void> setup({
);
});
getIt.registerFactory(
() => SendTemplatePage(sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
if (DeviceInfo.instance.isMobile) {
getIt.registerFactory(
@@ -1017,27 +884,6 @@ Future<void> setup({
'Unexpected wallet type: ${wallet.type} for generate Monero AccountListViewModel');
});
getIt.registerFactory(
() => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
getIt.registerFactory(
() => NanoAccountListPage(accountListViewModel: getIt.get<NanoAccountListViewModel>()));
/*getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
if (wallet is MoneroWallet) {
return MoneroAccountEditOrCreateViewModel(wallet.accountList);
}
// FIXME: throw exception.
return null;
});
getIt.registerFactory(() => MoneroAccountEditOrCreatePage(
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel, AccountListItem?, void>(
(AccountListItem? account, _) => MoneroAccountEditOrCreateViewModel(
monero!.getAccountList(getIt.get<AppStore>().wallet!),
@@ -1045,11 +891,6 @@ Future<void> setup({
wallet: getIt.get<AppStore>().wallet!,
accountListItem: account));
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem?, void>(
(AccountListItem? account, _) => MoneroAccountEditOrCreatePage(
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactoryParam<NanoAccountEditOrCreateViewModel, NanoAccount?, void>(
(NanoAccount? account, _) =>
NanoAccountEditOrCreateViewModel(nano!.getAccountList(getIt.get<AppStore>().wallet!),
@@ -1057,11 +898,6 @@ Future<void> setup({
wallet: getIt.get<AppStore>().wallet!,
accountListItem: account));
getIt.registerFactoryParam<NanoAccountEditOrCreatePage, NanoAccount?, void>(
(NanoAccount? account, _) => NanoAccountEditOrCreatePage(
nanoAccountCreationViewModel:
getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactory(
() => DisplaySettingsViewModel(getIt.get<AppStore>(), getIt.get<ThemeStore>()));
@@ -1129,16 +965,16 @@ Future<void> setup({
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
getIt.registerFactory(() => AddressListPage(getIt.get<WalletAddressListViewModel>()));
getIt.registerFactoryParam<NodeListViewModel, bool, void>((isPow, _) {
final appStore = getIt.get<AppStore>();
return NodeListViewModel(appStore, isPow);
});
getIt.registerFactoryParam<NewAddressesPage, bool, void>(
(showHidden, _) => NewAddressesPage(
showHidden: showHidden,
getIt.registerFactoryParam<NewAddressesPage, AddressesPageArgs, void>(
(args, _) => NewAddressesPage(
showHidden: args.showHidden,
popOnSelection: args.popOnSelection,
addressListViewModel: getIt<WalletAddressListViewModel>(),
dashboardViewModel: getIt<DashboardViewModel>(),
),
@@ -1154,7 +990,6 @@ Future<void> setup({
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
getIt.registerFactory(() => TrocadorProvidersPage(getIt.get<TrocadorProvidersViewModel>()));
getIt.registerFactory(() => DomainLookupsPage(getIt.get<ConnectionSyncViewModel>()));
@@ -1239,8 +1074,6 @@ Future<void> setup({
wallet: getIt.get<AppStore>().wallet!,
));
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
getIt.registerFactory<FeesViewModel>(
() => FeesViewModel(
getIt.get<AppStore>(),
@@ -1258,11 +1091,6 @@ Future<void> setup({
),
);
getIt.registerFactoryParam<ExchangePage, PaymentRequest?, void>(
(PaymentRequest? paymentRequest, __) {
return ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>(),
getIt.get<AddressResolverService>(), paymentRequest);
});
getIt.registerFactoryParam<NewSwapPage, PaymentRequest?, CryptoCurrency?>(
(PaymentRequest? paymentRequest, CryptoCurrency? initialCurrency) {
@@ -1276,17 +1104,9 @@ Future<void> setup({
);
});
getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
getIt.registerFactory(
() => ExchangeTradePage(exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
getIt.registerFactory(() =>
ExchangeTradeExternalSendPage(exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
getIt.registerFactory(() => BackgroundSyncPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
getIt.registerFactoryParam<SwapConfirmationBottomSheet, PaymentFlowResult, void>(
(paymentFlowResult, _) => SwapConfirmationBottomSheet(
@@ -1394,8 +1214,6 @@ Future<void> setup({
getIt.registerFactory(() => RescanPage(getIt.get<RescanViewModel>()));
getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, Map<String, dynamic>?>(
(type, additionalParams) {
final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?;
@@ -1470,17 +1288,12 @@ Future<void> setup({
getIt.registerFactory<PreSeedPage>(() => PreSeedPage(getIt.get<AppStore>().wallet!));
getIt.registerFactoryParam<TransactionSuccessPage, String, void>(
(content, _) => TransactionSuccessPage(content: content));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>(
(trade, _) => TradeDetailsViewModel(tradeForDetails: trade, appStore: getIt.get<AppStore>()));
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
getIt.registerFactory(() => CakeFeaturesPage(
dashboardViewModel: getIt.get<DashboardViewModel>(),
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>()));
dashboardViewModel: getIt.get<DashboardViewModel>(),));
getIt.registerFactory(() => BackupServiceV3(getIt.get<SecureStorage>(),
_transactionDescriptionBox, getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
@@ -1514,39 +1327,6 @@ Future<void> setup({
getIt.registerFactoryParam<NewBuySellAmountPage, BuySellPageMode, void>((mode, _) =>
NewBuySellAmountPage(buySellViewModel: getIt.get<BuySellViewModel>(param1: mode)));
getIt.registerFactory(
() => BuySellPage(getIt.get<BuySellViewModel>(), getIt.get<AddressResolverService>()));
getIt.registerFactoryParam<BuyOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
final items = args.first as List<SelectableItem>;
final pickAnOption = args[1] as void Function(SelectableOption option)?;
final confirmOption = args[2] as void Function(BuildContext contex)?;
return BuyOptionsPage(items: items, pickAnOption: pickAnOption, confirmOption: confirmOption);
});
getIt
.registerFactoryParam<PaymentMethodOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
final items = args.first as List<SelectableOption>;
final pickAnOption = args[1] as void Function(SelectableOption option)?;
return PaymentMethodOptionsPage(items: items, pickAnOption: pickAnOption);
});
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
return BuyViewModel(_ordersSource, getIt.get<OrdersStore>(), getIt.get<SettingsStore>(),
getIt.get<BuyAmountViewModel>(),
wallet: wallet!);
});
getIt.registerFactoryParam<BuyWebViewPage, List<dynamic>, void>((List<dynamic> args, _) {
final url = args.first as String;
final buyViewModel = args[1] as BuyViewModel;
return BuyWebViewPage(
buyViewModel: buyViewModel, ordersStore: getIt.get<OrdersStore>(), url: url);
});
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) {
final wallet = getIt.get<AppStore>().wallet;
@@ -1584,11 +1364,6 @@ Future<void> setup({
);
});
getIt.registerFactoryParam<UnspentCoinsListPage, UnspentCoinType?, void>(
(coinTypeToSpendFrom, _) => UnspentCoinsListPage(
unspentCoinsListViewModel:
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom)));
getIt.registerFactoryParam<NewCoinControlPage, UnspentCoinType?, bool?>(
(coinTypeToSpendFrom, canEdit) => NewCoinControlPage(
unspentCoinsListViewModel:
@@ -1665,11 +1440,6 @@ Future<void> setup({
useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory(() =>
DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>(), getIt.get<AuthService>()));
getIt.registerFactory(() => DesktopSidebarViewModel());
getIt.registerFactoryParam<AnonpayDetailsViewModel, AnonpayInvoiceInfo, void>(
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsViewModel(
anonPayApi: getIt.get<AnonPayApi>(),
@@ -1685,10 +1455,6 @@ Future<void> setup({
themeStore: getIt.get<ThemeStore>(),
));
getIt.registerFactoryParam<AnonPayReceivePage, AnonPayReceivePageArgs, void>(
(AnonPayReceivePageArgs anonpayReceivePageArgs, _) =>
AnonPayReceivePage(args: anonpayReceivePageArgs));
getIt.registerFactoryParam<AnonpayDetailsPage, AnonpayInvoiceInfo, void>(
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
+18 -3
View File
@@ -27,16 +27,26 @@ import 'package:cake_wallet/utils/list_item.dart';
import 'package:mobx/mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class AddressesPageArgs {
AddressesPageArgs({required this.showHidden, this.popOnSelection = false});
final bool showHidden;
final bool popOnSelection;
}
class NewAddressesPage extends StatefulWidget {
const NewAddressesPage(
{super.key,
required this.addressListViewModel,
required this.dashboardViewModel,
required this.showHidden});
required this.showHidden, required this.popOnSelection});
final WalletAddressListViewModel addressListViewModel;
final DashboardViewModel dashboardViewModel;
final bool showHidden;
final bool popOnSelection;
@override
State<NewAddressesPage> createState() => _NewAddressesPageState();
@@ -133,6 +143,7 @@ class _NewAddressesPageState extends State<NewAddressesPage> {
fontSize: 10,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
if(!widget.popOnSelection)
ShowHiddenButton()
],
),
@@ -148,11 +159,15 @@ class _NewAddressesPageState extends State<NewAddressesPage> {
builder: (_) => AddressRow(
selected:
item.address == widget.addressListViewModel.address.address,
first: widget.showHidden && index == 0,
first: (widget.showHidden || widget.popOnSelection) && index == 0,
last: index == filteredItems.length - 1,
item: item,
onSelect: () {
widget.addressListViewModel.setAddress(item);
if(widget.popOnSelection) {
Navigator.of(context).pop(item.address);
} else {
widget.addressListViewModel.setAddress(item);
}
},
onLabelChanged: updateItems,
onAddressHidden: () async {
+7 -48
View File
@@ -20,7 +20,6 @@ import "package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.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/receive/anonpay_receive_page.dart";
import "package:cake_wallet/utils/share_util.dart";
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
import "package:cake_wallet/view_model/dashboard/receive_option_view_model.dart";
@@ -116,53 +115,13 @@ class _NewReceivePageState extends State<NewReceivePage> {
return;
}
switch (option) {
case ReceivePageOption.anonPayInvoice:
Navigator.pushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [widget.addressListViewModel.address.address, option],
);
break;
case ReceivePageOption.anonPayDonationLink:
final sharedPreferences = getIt.get<SharedPreferences>();
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
final donationWalletName =
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
if (clearnetUrl != null &&
onionUrl != null &&
widget.addressListViewModel.wallet.name == donationWalletName) {
Navigator.pushNamed(
context,
Routes.anonPayReceivePage,
arguments: AnonPayReceivePageArgs(
invoiceInfo: AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl,
onionUrl: onionUrl,
address: widget.addressListViewModel.address.address,
),
qrImage: widget.addressListViewModel.qrImage,
),
);
} else {
Navigator.pushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [widget.addressListViewModel.address.address, option],
);
}
break;
default:
if ([WalletType.bitcoin, WalletType.litecoin]
.contains(widget.addressListViewModel.type)) {
widget.addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
}
if (widget.addressListViewModel.type == WalletType.zcash) {
printV("help me i'll kms if that wont work: ${zcash!.getZcashAddressType(option)}");
widget.addressListViewModel.setAddressType(zcash!.getZcashAddressType(option));
}
if ([WalletType.bitcoin, WalletType.litecoin]
.contains(widget.addressListViewModel.type)) {
widget.addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
}
if (widget.addressListViewModel.type == WalletType.zcash) {
printV("help me i'll kms if that wont work: ${zcash!.getZcashAddressType(option)}");
widget.addressListViewModel.setAddressType(zcash!.getZcashAddressType(option));
}
});
}
@@ -53,7 +53,6 @@ class CoinActionRow extends StatelessWidget {
),
label: S.of(context).send,
action: () {
if (FeatureFlag.hasNewUiExtraPages) {
final sendPage = getIt.get<NewSendPage>(
param1: SendPageParams(
unspentCoinType:
@@ -74,11 +73,6 @@ class CoinActionRow extends StatelessWidget {
);
},
);
} else {
Map<String, dynamic>? args;
if (lightningMode) args = {'coinTypeToSpendFrom': UnspentCoinType.lightning};
Navigator.of(context).pushNamed(Routes.send, arguments: args);
}
},
),
CoinActionButton(
@@ -91,7 +85,6 @@ class CoinActionRow extends StatelessWidget {
),
label: S.of(context).receive,
action: () async {
if (FeatureFlag.hasNewUiExtraPages) {
final page = getIt.get<NewReceivePage>(param1: lightningMode);
CupertinoScaffold.showCupertinoModalBottomSheet(
context: context,
@@ -100,17 +93,6 @@ class CoinActionRow extends StatelessWidget {
return Material(child: ModalNavigator(parentContext: context, rootPage: page));
},
);
} else {
// ToDo: (Konsti) refactor as part of the derivation PR (I hate myself for it)
if (lightningMode) {
await getIt<WalletAddressListViewModel>().setAddressType(
bitcoin!.getOptionToType(bitcoin!.getBitcoinLightningReceivePageOption()));
} else {
await getIt<WalletAddressListViewModel>().setAddressType(
bitcoin!.getOptionToType(bitcoin!.getBitcoinSegwitPageOption()));
}
Navigator.of(context).pushNamed(Routes.addressPage);
}
},
),
if (showSwap)
@@ -126,7 +108,6 @@ class CoinActionRow extends StatelessWidget {
action: () {
final page =
getIt.get<NewSwapPage>(param2: lightningMode ? CryptoCurrency.btcln : null);
if (FeatureFlag.hasNewUiExtraPages) {
CupertinoScaffold.showCupertinoModalBottomSheet(
context: context,
barrierColor: Colors.black.withAlpha(85),
@@ -136,9 +117,6 @@ class CoinActionRow extends StatelessWidget {
parentContext: context,
)),
);
} else {
Navigator.of(context).pushNamed(Routes.exchange);
}
},
),
CoinActionButton(
@@ -349,7 +349,7 @@ class _CardsViewState extends State<CardsView> {
}
}
if (FeatureFlag.hasNewUiExtraPages && widget.dashboardViewModel.type == WalletType.bitcoin) {
if (widget.dashboardViewModel.type == WalletType.bitcoin) {
final page = getIt.get<NewSendPage>(
param1: SendPageParams(
initialPaymentRequest: paymentRequest,
@@ -396,7 +396,7 @@ class _CardsViewState extends State<CardsView> {
unspentCoinType = UnspentCoinType.lightning;
}
if (FeatureFlag.hasNewUiExtraPages && widget.dashboardViewModel.type == WalletType.bitcoin) {
if (widget.dashboardViewModel.type == WalletType.bitcoin) {
final page = getIt.get<NewSendPage>(
param1: SendPageParams(
initialPaymentRequest: paymentRequest,
+9 -131
View File
@@ -17,6 +17,7 @@ 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/pages/receive_page.dart";
import 'package:cake_wallet/new-ui/pages/send_page.dart';
import "package:cake_wallet/new-ui/pages/swap_page.dart";
import "package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart";
import 'package:cake_wallet/new-ui/widgets/hardware_wallet/sync_key_images_sheet.dart';
import 'package:cake_wallet/order/order.dart';
@@ -33,25 +34,17 @@ import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dar
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
import "package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart";
import 'package:cake_wallet/src/screens/connect_device/select_device_manufacturer_page.dart';
import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
import "package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart";
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart';
import "package:cake_wallet/src/screens/dashboard/pages/nft_send_page.dart";
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
@@ -67,16 +60,8 @@ import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/socket_health_logs_page.dart';
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
import 'package:cake_wallet/src/screens/disclaimer/third_party_disclaimer_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
import 'package:cake_wallet/src/screens/faq/faq_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/wallet_group_display_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/advanced_privacy_settings_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
@@ -88,24 +73,16 @@ import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart'
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
import 'package:cake_wallet/src/screens/payjoin_details/payjoin_details_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@@ -117,7 +94,6 @@ import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_logs_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
@@ -125,7 +101,6 @@ import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
import 'package:cake_wallet/src/screens/start_tor/start_tor_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/support/support_page.dart';
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
@@ -174,7 +149,6 @@ import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'src/screens/buy/buy_sell_page.dart';
import 'src/screens/dashboard/pages/nft_import_page.dart';
late RouteSettings currentRouteSettings;
@@ -427,7 +401,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.receiveAddresses:
return handleRouteWithPlatformAwareness(
(context) => getIt.get<NewAddressesPage>(param1: settings.arguments as bool));
(context) => getIt.get<NewAddressesPage>(param1: AddressesPageArgs(showHidden:settings.arguments as bool)));
case Routes.seed:
return handleRouteWithPlatformAwareness(
@@ -445,14 +419,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
param1: settings.arguments as List<DerivationInfo>));
case Routes.sweepingWalletPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
case Routes.dashboard:
return CupertinoPageRoute<void>(
settings: settings,
builder: (_) =>
FeatureFlag.hasNewUi ? getIt.get<NewDashboard>() : getIt.get<DashboardPage>());
builder: (_) => getIt.get<NewDashboard>());
case Routes.send:
final args = settings.arguments as Map<String, dynamic>?;
@@ -472,27 +443,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
settings: settings,
);
case Routes.sendTemplate:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
case Routes.receive:
return CupertinoPageRoute<void>(
builder: (context) => getIt.get<ReceivePage>(), settings: settings);
case Routes.addressPage:
return handleRouteWithPlatformAwareness((context) => getIt.get<AddressPage>(),
settings: settings);
case Routes.newReceivePage:
if (FeatureFlag.hasNewUi) {
return handleRouteWithPlatformAwareness(
(context) => Material(child: getIt.get<NewReceivePage>(param1: false, param2: null)),
settings: settings,
);
}
return handleRouteWithPlatformAwareness((context) => getIt.get<AddressPage>(),
settings: settings);
case Routes.transactionDetails:
return CupertinoPageRoute<void>(
@@ -505,9 +461,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true,
builder: (_) => getIt.get<RBFDetailsPage>(param1: settings.arguments as List<dynamic>));
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
case Routes.disclaimer:
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
@@ -625,9 +578,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
(context) => getIt.get<PrivacyPage>(),
);
case Routes.trocadorProvidersPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<TrocadorProvidersPage>());
case Routes.domainLookupsPage:
return CupertinoPageRoute<void>(
@@ -667,11 +617,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?);
return CupertinoPageRoute<void>(builder: (_) => page);
case Routes.accountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem?));
case Routes.accountCustomizer:
return handleRouteWithPlatformAwareness(
(_) => getIt.get<AccountCustomizer>(
@@ -679,11 +624,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
),
);
case Routes.nanoAccountCreation:
return CupertinoPageRoute<String>(
builder: (_) =>
getIt.get<NanoAccountEditOrCreatePage>(param1: settings.arguments as NanoAccount?));
case Routes.addressBook:
return handleRouteWithPlatformAwareness(
(context) => getIt.get<ContactListPage>(),
@@ -695,7 +635,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<ContactListPage>(param1: args[0], param2: args[1]));
case Routes.pickerWalletAddress:
return MaterialPageRoute<void>(builder: (_) => getIt.get<AddressListPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<NewAddressesPage>(param1: AddressesPageArgs(showHidden: false, popOnSelection: true)));
case Routes.addressBookAddContact:
return handleRouteWithPlatformAwareness(
@@ -712,12 +652,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
(context) => getIt.get<ShowKeysDisclaimerPage>(),
);
case Routes.exchangeTrade:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTradePage>());
case Routes.exchangeConfirm:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeConfirmPage>());
case Routes.tradeDetails:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
@@ -732,35 +666,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
(context) => Material(child: getIt.get<NewBuySellAmountPage>(param1: BuySellPageMode.buy)),
);
case Routes.buyOptionsPage:
final args = settings.arguments as List;
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>(param1: args));
case Routes.paymentMethodOptionsPage:
final args = settings.arguments as List;
return MaterialPageRoute<void>(
builder: (_) => getIt.get<PaymentMethodOptionsPage>(param1: args));
case Routes.buyWebView:
final args = settings.arguments as List;
return MaterialPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
case Routes.exchange:
return handleRouteWithPlatformAwareness(
(context) => getIt.get<ExchangePage>(param1: settings.arguments as PaymentRequest?),
(context) => Material(
child: getIt.get<NewSwapPage>(param1: settings.arguments as PaymentRequest?)),
settings: settings,
);
case Routes.exchangeTemplate:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
case Routes.rescan:
final page = getIt.get<RescanPage>();
return MaterialPageRoute<void>(builder: (_) => page);
case Routes.faq:
return MaterialPageRoute<void>(builder: (_) => getIt.get<FaqPage>());
case Routes.preSeedPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<PreSeedPage>());
@@ -768,10 +686,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletGroupExistingSeedDescriptionPage:
return MaterialPageRoute<void>(builder: (_) => WalletGroupExistingSeedDescriptionPage());
case Routes.transactionSuccessPage:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<TransactionSuccessPage>(param1: settings.arguments as String));
case Routes.backup:
return handleRouteWithPlatformAwareness(
(context) => getIt.get<BackupPage>(),
@@ -845,13 +759,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
(context) => getIt.get<CakePayAccountPage>(),
);
case Routes.webViewPage:
final args = settings.arguments as List;
final title = args.first as String;
final url = args[1] as Uri;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
case Routes.advancedPrivacySettings:
final args = settings.arguments as Map<String, dynamic>;
final type = args['type'] as WalletType;
@@ -879,15 +786,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
),
);
case Routes.anonPayInvoicePage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage:
final anonReceivePageArgs = settings.arguments as AnonPayReceivePageArgs;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonReceivePageArgs));
case Routes.anonPayDetailsPage:
final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo;
return CupertinoPageRoute<void>(
@@ -901,24 +799,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) =>
getIt.get<PayjoinDetailsPage>(param1: sessionId, param2: transactionInfo));
case Routes.desktop_actions:
return PageRouteBuilder(
opaque: false,
pageBuilder: (_, __, ___) => DesktopDashboardActions(getIt<DashboardViewModel>()),
);
case Routes.desktop_settings_page:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<DesktopSettingsPage>());
case Routes.empty_no_route:
return MaterialPageRoute<void>(builder: (_) => SizedBox.shrink());
case Routes.transactionsPage:
return CupertinoPageRoute<void>(
settings: settings,
fullscreenDialog: true,
builder: (_) => getIt.get<TransactionsPage>());
case Routes.setup_2faPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAPage>());
@@ -1032,11 +915,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<SeedVerificationPage>(param1: settings.arguments as bool),
);
case Routes.exchangeTradeExternalSendPage:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<ExchangeTradeExternalSendPage>(),
);
case Routes.backgroundSync:
return handleRouteWithPlatformAwareness(
(context) => getIt.get<BackgroundSyncPage>(),
@@ -1,48 +0,0 @@
import 'package:cake_wallet/core/selectable_option.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/select_options_page.dart';
import 'package:flutter/cupertino.dart';
class BuyOptionsPage extends SelectOptionsPage {
BuyOptionsPage({required this.items, this.pickAnOption, this.confirmOption});
final List<SelectableItem> items;
final Function(SelectableOption option)? pickAnOption;
final Function(BuildContext context)? confirmOption;
@override
String get pageTitle => S.current.choose_a_provider;
@override
EdgeInsets? get contentPadding => null;
@override
EdgeInsets? get tilePadding => EdgeInsets.only(top: 8);
@override
EdgeInsets? get innerPadding => EdgeInsets.symmetric(horizontal: 24, vertical: 8);
@override
double? get imageHeight => 40;
@override
double? get imageWidth => 40;
@override
Color? get selectedBackgroundColor => null;
@override
double? get tileBorderRadius => 30;
@override
String get bottomSectionText => '';
@override
void Function(SelectableOption option)? get onOptionTap => pickAnOption;
@override
String get primaryButtonText => S.current.confirm;
@override
void Function(BuildContext context)? get primaryButtonAction => confirmOption;
}
-511
View File
@@ -1,511 +0,0 @@
import 'package:cake_wallet/buy/sell_buy_states.dart';
import 'package:cake_wallet/core/address_resolver/address_resolver_service.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.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/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/provider_optoin_tile.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
class BuySellPage extends BasePage {
BuySellPage(this.buySellViewModel, this._resolver);
final BuySellViewModel buySellViewModel;
final AddressResolverService _resolver;
final cryptoCurrencyKey = GlobalKey<ExchangeCardState>();
final fiatCurrencyKey = GlobalKey<ExchangeCardState>();
final _formKey = GlobalKey<FormState>();
final _fiatAmountFocus = FocusNode();
final _cryptoAmountFocus = FocusNode();
final _cryptoAddressFocus = FocusNode();
var _isReactionsSet = false;
late final String? depositWalletName;
late final String? receiveWalletName;
@override
String get title => S.current.buy + '/' + S.current.sell;
@override
bool get gradientBackground => true;
@override
bool get gradientAll => true;
@override
bool get resizeToAvoidBottomInset => false;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.completelyTransparent;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).clear,
onPressed: () {
_formKey.currentState?.reset();
buySellViewModel.reset();
});
@override
Widget? leading(BuildContext context) {
final _backButton = Icon(
Icons.arrow_back_ios,
color: Theme.of(context).colorScheme.primary,
size: 16,
);
final _closeButton = currentTheme.isDark ? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
return MergeSemantics(
child: SizedBox(
height: isMobileView ? 37 : 45,
width: isMobileView ? 37 : 45,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
child: TextButton(
style: ButtonStyle(
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent),
),
onPressed: () => onClose(context),
child: !isMobileView ? _closeButton : _backButton,
),
),
),
),
);
}
@override
Widget body(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, buySellViewModel));
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).colorScheme.surface,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _fiatAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]),
KeyboardActionsItem(
focusNode: _cryptoAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()])
]),
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Observer(
builder: (_) => Column(
children: [
_exchangeCardsSection(context),
Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Column(
children: [
SizedBox(height: 12),
_buildPaymentMethodTile(context),
],
),
),
],
),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(
builder: (_) => Column(
children: [
if (buySellViewModel.isBuySellQuoteFailed)
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Row(
children: [
Expanded(
child: Container(
alignment: Alignment.centerRight,
child: Icon(
Icons.warning_amber_rounded,
color: Theme.of(context).colorScheme.error,
size: 26,
),
),
),
Expanded(
flex: 8,
child: Text(
buySellViewModel.buySellQuoteFailedError ??
S.of(context).buy_sell_pair_is_not_supported_warning,
textAlign: TextAlign.center,
softWrap: true,
overflow: TextOverflow.ellipsis,
maxLines: 3,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
LoadingPrimaryButton(
text: S.current.choose_a_provider,
onPressed: () async {
if (!_formKey.currentState!.validate()) return;
buySellViewModel.onTapChoseProvider(context);
},
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: buySellViewModel.isBuySellQuoteFailed,
isLoading:
!buySellViewModel.isReadyToTrade && !buySellViewModel.isBuySellQuoteFailed,
),
],
),
),
),
),
),
);
}
Widget _buildPaymentMethodTile(BuildContext context) {
if (buySellViewModel.paymentMethodState is PaymentMethodLoading ||
buySellViewModel.paymentMethodState is InitialPaymentMethod) {
return OptionTilePlaceholder(
withBadge: false,
withSubtitle: false,
borderRadius: 30,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
leadingIcon: Icons.arrow_forward_ios,
isDarkTheme: currentTheme.isDark);
}
if (buySellViewModel.paymentMethodState is PaymentMethodFailed) {
return OptionTilePlaceholder(errorText: 'No payment methods available', borderRadius: 30);
}
if (buySellViewModel.paymentMethodState is PaymentMethodLoaded &&
buySellViewModel.selectedPaymentMethod != null) {
final selectedPaymentMethod = buySellViewModel.selectedPaymentMethod!;
return ProviderOptionTile(
lightImagePath: selectedPaymentMethod.lightIconPath,
darkImagePath: selectedPaymentMethod.darkIconPath,
title: selectedPaymentMethod.title,
onPressed: () => _pickPaymentMethod(context),
leadingIcon: Icons.arrow_forward_ios,
isLightMode: !currentTheme.isDark,
borderRadius: 30,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
titleTextStyle: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
);
}
return OptionTilePlaceholder(errorText: 'No payment methods available', borderRadius: 30);
}
void _pickPaymentMethod(BuildContext context) async {
final currentOption = buySellViewModel.selectedPaymentMethod;
await Navigator.of(context).pushNamed(
Routes.paymentMethodOptionsPage,
arguments: [
buySellViewModel.paymentMethods,
buySellViewModel.changeOption,
],
);
buySellViewModel.selectedPaymentMethod;
if (currentOption != null &&
currentOption.paymentMethodType !=
buySellViewModel.selectedPaymentMethod?.paymentMethodType) {
await buySellViewModel.calculateBestRate();
}
}
void _setReactions(BuildContext context, BuySellViewModel buySellViewModel) {
if (_isReactionsSet) return;
final fiatAmountController = fiatCurrencyKey.currentState!.amountController;
final cryptoAmountController = cryptoCurrencyKey.currentState!.amountController;
final cryptoAddressController = cryptoCurrencyKey.currentState!.addressController;
cryptoAddressController.addListener(() {
buySellViewModel.changeCryptoCurrencyAddress(cryptoAddressController.text);
});
_onCurrencyChange(buySellViewModel.cryptoCurrency, buySellViewModel, cryptoCurrencyKey);
_onCurrencyChange(buySellViewModel.fiatCurrency, buySellViewModel, fiatCurrencyKey);
reaction(
(_) => buySellViewModel.wallet.name,
(_) => _onWalletNameChange(
buySellViewModel, buySellViewModel.cryptoCurrency, cryptoCurrencyKey));
reaction((_) => buySellViewModel.cryptoCurrency,
(currency) => _onCurrencyChange(currency, buySellViewModel, cryptoCurrencyKey));
reaction((_) => buySellViewModel.fiatCurrency,
(currency) => _onCurrencyChange(currency, buySellViewModel, fiatCurrencyKey));
reaction((_) => buySellViewModel.fiatAmount, (amount) {
if (fiatCurrencyKey.currentState!.amountController.text != amount) {
fiatCurrencyKey.currentState!.amountController.text = amount;
}
});
reaction((_) => buySellViewModel.isCryptoCurrencyAddressEnabled, (isEnabled) {
cryptoCurrencyKey.currentState!.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => buySellViewModel.cryptoAmount, (amount) {
if (cryptoCurrencyKey.currentState!.amountController.text != amount) {
cryptoCurrencyKey.currentState!.amountController.text = amount;
}
});
reaction((_) => buySellViewModel.cryptoCurrencyAddress, (String address) {
if (cryptoAddressController.text != address) {
cryptoCurrencyKey.currentState!.addressController.text = address;
}
});
fiatAmountController.addListener(() {
if (fiatAmountController.text != buySellViewModel.fiatAmount) {
buySellViewModel.changeFiatAmount(amount: fiatAmountController.text);
}
});
cryptoAmountController.addListener(() {
if (cryptoAmountController.text != buySellViewModel.cryptoAmount) {
buySellViewModel.changeCryptoAmount(amount: cryptoAmountController.text);
}
});
_cryptoAddressFocus.addListener(() async {
if (!_cryptoAddressFocus.hasFocus && cryptoAddressController.text.isNotEmpty) {
final domain = cryptoAddressController.text;
final parsed = await fetchParsedAddress(context, domain, buySellViewModel.cryptoCurrency);
if (parsed.isNotEmpty) {
buySellViewModel.cryptoCurrencyAddress = parsed;
}
}
});
reaction((_) => buySellViewModel.wallet.walletAddresses.addressForBuy, (String address) {
if (buySellViewModel.cryptoCurrency == CryptoCurrency.xmr) {
cryptoCurrencyKey.currentState!.changeAddress(address: address);
}
});
reaction((_) => buySellViewModel.isReadyToTrade, (bool isReady) {
if (isReady) {
if (buySellViewModel.skipIsReadyToTradeReaction) {
buySellViewModel.skipIsReadyToTradeReaction = false;
return;
}
if (cryptoAmountController.text.isNotEmpty &&
cryptoAmountController.text != S.current.fetching) {
buySellViewModel.changeCryptoAmount(amount: cryptoAmountController.text);
} else if (fiatAmountController.text.isNotEmpty &&
fiatAmountController.text != S.current.fetching) {
buySellViewModel.changeFiatAmount(amount: fiatAmountController.text);
}
}
});
_isReactionsSet = true;
}
void _onCurrencyChange(
Currency currency, BuySellViewModel buySellViewModel, GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == buySellViewModel.wallet.currency;
key.currentState!.changeSelectedCurrency(currency);
key.currentState!.changeWalletName(isCurrentTypeWallet ? buySellViewModel.wallet.name : '');
key.currentState!.changeAddress(
address: isCurrentTypeWallet ? buySellViewModel.wallet.walletAddresses.addressForBuy : '');
key.currentState!.changeAmount(amount: '');
}
void _onWalletNameChange(BuySellViewModel buySellViewModel, CryptoCurrency currency,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == buySellViewModel.wallet.currency;
if (isCurrentTypeWallet) {
key.currentState!.changeWalletName(buySellViewModel.wallet.name);
key.currentState!.addressController.text =
buySellViewModel.wallet.walletAddresses.addressForBuy;
} else if (key.currentState!.addressController.text ==
buySellViewModel.wallet.walletAddresses.addressForBuy) {
key.currentState!.changeWalletName('');
key.currentState!.addressController.text = '';
}
}
void disposeBestRateSync() => {};
Widget _exchangeCardsSection(BuildContext context) {
final fiatExchangeCard = Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'fiat_currency_trade_card',
onDispose: disposeBestRateSync,
amountFocusNode: _fiatAmountFocus,
key: fiatCurrencyKey,
title: 'FIAT ${S.of(context).amount}',
initialCurrency: buySellViewModel.fiatCurrency,
initialWalletName: '',
initialAddress: '',
initialIsAmountEditable: true,
isAmountEstimated: false,
currencyRowPadding: EdgeInsets.zero,
addressRowPadding: EdgeInsets.zero,
isMoneroWallet: buySellViewModel.wallet == WalletType.monero,
showAddressField: false,
showLimitsField: false,
currencies: buySellViewModel.fiatCurrencies,
onCurrencySelected: (currency) => buySellViewModel.changeFiatCurrency(currency: currency),
onTapCurrencyPicker: _presentFiatPicker,
imageArrow: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).colorScheme.primary,
height: 8,
),
currencyButtonColor: Colors.transparent,
addressButtonsColor: Theme.of(context).colorScheme.surfaceContainerHighest,
borderColor: Theme.of(context).colorScheme.outlineVariant,
onPushPasteButton: (context) async {},
onPushAddressBookButton: (context) async {},
fillColor: buySellViewModel.mode == BuySellPageMode.buy
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
),
);
final cryptoExchangeCard = Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'crypto_currency_trade_card',
onDispose: disposeBestRateSync,
amountFocusNode: _cryptoAmountFocus,
addressFocusNode: _cryptoAddressFocus,
key: cryptoCurrencyKey,
title: 'Crypto ${S.of(context).amount}',
initialCurrency: buySellViewModel.cryptoCurrency,
initialWalletName: '',
initialAddress: buySellViewModel.cryptoCurrency == buySellViewModel.wallet.currency
? buySellViewModel.wallet.walletAddresses.addressForBuy
: buySellViewModel.cryptoCurrencyAddress,
initialIsAmountEditable: true,
isAmountEstimated: true,
showLimitsField: false,
currencyRowPadding: EdgeInsets.zero,
addressRowPadding: EdgeInsets.zero,
isMoneroWallet: buySellViewModel.wallet == WalletType.monero,
currencies: buySellViewModel.cryptoCurrencies,
onCurrencySelected: (currency) => buySellViewModel.changeCryptoCurrency(currency: currency),
onTapCurrencyPicker: _presentCryptoPicker,
imageArrow: Image.asset(
'assets/images/arrow_bottom_cake_green.png',
color: Theme.of(context).colorScheme.primary,
height: 8,
),
currencyButtonColor: Colors.transparent,
addressButtonsColor: Theme.of(context).colorScheme.surfaceContainerHighest,
borderColor: Theme.of(context).colorScheme.outlineVariant,
addressTextFieldValidator: AddressValidator(type: buySellViewModel.cryptoCurrency),
onPushPasteButton: (context) async {},
onPushAddressBookButton: (context) async {},
fillColor: buySellViewModel.mode == BuySellPageMode.buy
? Theme.of(context).colorScheme.surfaceContainerLow
: Theme.of(context).colorScheme.surfaceContainer,
useSatoshis: buySellViewModel.useSatoshi,
),
);
if (responsiveLayoutUtil.shouldRenderMobileUI) {
return Observer(
builder: (_) {
return Placeholder();
},
);
}
return Observer(
builder: (_) {
return Placeholder();
},
);
}
void _presentFiatPicker(BuildContext context) {
FiatCurrencyPickerSheet.show(
context: context,
selected: buySellViewModel.fiatCurrency,
onSelected: (currency) => buySellViewModel.changeFiatCurrency(currency: currency),
);
}
void _presentCryptoPicker(BuildContext context) {
final items = [...buySellViewModel.cryptoCurrencies];
appendEvmDefaultTokens(items);
CurrencyPickerSheet.show(
context: context,
args: CurrencyPickerArgs(
items: items,
selected: buySellViewModel.cryptoCurrency,
recentsSource: RecentsSource.orders,
onSelected: (currency) => buySellViewModel.changeCryptoCurrency(currency: currency),
symbolResolver: buySellViewModel.amountParsingProxy.getCryptoSymbol,
),
);
}
Future<String> fetchParsedAddress(
BuildContext context,
String domain,
CryptoCurrency currency,
) async {
final parsedAddresses =
await _resolver.resolve(query: domain, wallet: buySellViewModel.wallet, currency: currency);
return parsedAddresses.isNotEmpty
? parsedAddresses.first.parsedAddressByCurrencyMap[currency] ?? ''
: '';
}
}
-108
View File
@@ -1,108 +0,0 @@
import 'dart:async';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/store/dashboard/orders_store.dart';
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class BuyWebViewPage extends BasePage {
BuyWebViewPage({required this.buyViewModel, required this.ordersStore, required this.url});
final OrdersStore ordersStore;
final String url;
final BuyViewModel buyViewModel;
@override
String get title => S.current.buy;
@override
Widget body(BuildContext context) =>
BuyWebViewPageBody(buyViewModel, ordersStore: ordersStore, url: url);
}
class BuyWebViewPageBody extends StatefulWidget {
BuyWebViewPageBody(this.buyViewModel, {required this.ordersStore, this.url});
final OrdersStore ordersStore;
final String? url;
final BuyViewModel buyViewModel;
@override
BuyWebViewPageBodyState createState() => BuyWebViewPageBodyState();
}
class BuyWebViewPageBodyState extends State<BuyWebViewPageBody> {
BuyWebViewPageBodyState()
: _webViewkey = GlobalKey(),
_isSaving = false,
orderId = '';
String orderId;
InAppWebViewController? _webViewController;
GlobalKey _webViewkey;
Timer? _timer;
bool _isSaving;
@override
void initState() {
super.initState();
_webViewkey = GlobalKey();
_isSaving = false;
widget.ordersStore.orderId = '';
if (widget.buyViewModel.selectedProvider is WyreBuyProvider) {
_saveOrder(keyword: 'completed', splitSymbol: '/');
}
if (widget.buyViewModel.selectedProvider is MoonPayProvider) {
_saveOrder(keyword: 'transactionId', splitSymbol: '=');
}
}
@override
Widget build(BuildContext context) {
return InAppWebView(
key: _webViewkey,
initialSettings: InAppWebViewSettings(
transparentBackground: true,
),
initialUrlRequest: URLRequest(url: WebUri(widget.url ?? '')),
onWebViewCreated: (InAppWebViewController controller) =>
setState(() => _webViewController = controller));
}
void _saveOrder({required String keyword, required String splitSymbol}) {
_timer?.cancel();
_timer = Timer.periodic(Duration(seconds: 1), (timer) async {
try {
if (_webViewController == null || _isSaving) {
return;
}
final url = (await _webViewController!.getUrl())?.toString();
if (url == null) {
throw Exception('_saveOrder: Url is null');
}
if (url.contains(keyword)) {
final urlParts = url.split(splitSymbol);
orderId = urlParts.last;
widget.ordersStore.orderId = orderId;
if (orderId.isNotEmpty) {
_isSaving = true;
await widget.buyViewModel.saveOrder(orderId);
timer.cancel();
}
}
} catch (e) {
_isSaving = false;
printV(e);
}
});
}
}
@@ -1,47 +0,0 @@
import 'package:cake_wallet/core/selectable_option.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/select_options_page.dart';
import 'package:flutter/cupertino.dart';
class PaymentMethodOptionsPage extends SelectOptionsPage {
PaymentMethodOptionsPage({required this.items, this.pickAnOption});
final List<SelectableItem> items;
final Function(SelectableOption option)? pickAnOption;
@override
String get pageTitle => S.current.choose_a_payment_method;
@override
EdgeInsets? get contentPadding => null;
@override
EdgeInsets? get tilePadding => EdgeInsets.only(top: 12);
@override
EdgeInsets? get innerPadding => EdgeInsets.symmetric(horizontal: 24, vertical: 12);
@override
double? get imageHeight => null;
@override
double? get imageWidth => null;
@override
Color? get selectedBackgroundColor => null;
@override
double? get tileBorderRadius => 30;
@override
String get bottomSectionText => '';
@override
void Function(SelectableOption option)? get onOptionTap => pickAnOption;
@override
String get primaryButtonText => S.current.confirm;
@override
void Function(BuildContext context)? get primaryButtonAction => null;
}
-87
View File
@@ -1,87 +0,0 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WebViewPage extends BasePage {
WebViewPage(this._title, this._url);
final String _title;
final Uri _url;
@override
String get title => _title;
@override
Widget body(BuildContext context) {
return WebViewPageBody(_title, _url);
}
}
class WebViewPageBody extends StatefulWidget {
WebViewPageBody(this.title, this.uri);
final String title;
final Uri uri;
@override
WebViewPageBodyState createState() => WebViewPageBodyState();
}
class WebViewPageBodyState extends State<WebViewPageBody> {
WebViewPageBodyState();
@override
Widget build(BuildContext context) {
return InAppWebView(
initialSettings: InAppWebViewSettings(
transparentBackground: true,
),
initialUrlRequest: URLRequest(url: WebUri.uri(widget.uri)),
onPermissionRequest: (controller, request) async {
final sharedPrefs = getIt.get<SharedPreferences>();
if (sharedPrefs.getBool(PreferencesKey.showCameraConsent) ?? true) {
final bool userConsent = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).privacy,
alertContent: S.of(context).camera_consent(widget.title),
rightButtonText: S.of(context).agree,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
/// if user did NOT give the consent then return permission denied
if (!userConsent) {
return PermissionResponse(
resources: request.resources,
action: PermissionResponseAction.DENY,
);
}
sharedPrefs.setBool(PreferencesKey.showCameraConsent, false);
}
bool permissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
return PermissionResponse(
resources: request.resources,
action:
permissionGranted ? PermissionResponseAction.GRANT : PermissionResponseAction.DENY,
);
},
);
}
}
@@ -1,236 +0,0 @@
// import 'dart:typed_data';
//
// import 'package:basic_utils/basic_utils.dart';
// import 'package:bitcoin_base/bitcoin_base.dart';
// import 'package:cake_wallet/src/screens/base_page.dart';
// import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
// import 'package:cake_wallet/src/widgets/primary_button.dart';
// import 'package:cake_wallet/utils/responsive_layout_util.dart';
// import 'package:flutter/material.dart';
// import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
// import 'package:ledger_litecoin/ledger_litecoin.dart';
// import 'package:permission_handler/permission_handler.dart';
//
// class DebugDevicePage extends BasePage {
// @override
// String get title => "Connect Ledger";
//
// @override
// Widget body(BuildContext context) => DebugDevicePageBody();
// }
//
// class DebugDevicePageBody extends StatefulWidget {
// @override
// DebugDevicePageBodyState createState() => DebugDevicePageBodyState();
// }
//
// class DebugDevicePageBodyState extends State<DebugDevicePageBody> {
// final imageLedger = Image.asset(
// 'assets/images/ledger_icon_black.png',
// width: 40,
// );
// final ledger = Ledger(
// options: LedgerOptions(
// scanMode: ScanMode.balanced,
// maxScanDuration: const Duration(milliseconds: 5000),
// ),
// onPermissionRequest: (status) async {
// Map<Permission, PermissionStatus> statuses = await [
// // Permission.location,
// Permission.bluetoothScan,
// Permission.bluetoothConnect,
// Permission.bluetoothAdvertise,
// ].request();
//
// if (status != BleStatus.ready) {
// return false;
// }
//
// return statuses.values.where((status) => status.isDenied).isEmpty;
// },
// );
//
// // late BitcoinLedgerApp btc;
// late LitecoinLedgerApp ltc;
//
// var devices = <LedgerDevice>[];
// var status = "";
// var counter = 0;
// LedgerDevice? selectedDevice = null;
//
// @override
// void initState() {
// super.initState();
// // btc = BitcoinLedgerApp(ledger);
// ltc = LitecoinLedgerApp(ledger);
// }
//
// @override
// void dispose() {
// super.dispose();
// ledger.close(ConnectionType.ble);
// ledger.close(ConnectionType.usb);
// }
//
// Future<void> reconnectCurrentDevice() async {
// // await ledger.disconnect(selectedDevice!);
// // await ledger.connect(selectedDevice!);
// }
//
// Future<void> disconnectCurrentDevice() async {
// await ledger.disconnect(selectedDevice!);
// setState(() => selectedDevice = null);
// }
//
// @override
// Widget build(BuildContext context) {
// final imageLedger = 'assets/images/hardware_wallet/ledger_nano_x.png';
//
// return Center(
// child: Container(
// width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
// height: double.infinity,
// padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
// child: SingleChildScrollView(
// child: Column(
// children: [
// Padding(
// padding: EdgeInsets.only(top: 20),
// child: Text(status),
// ),
// if (selectedDevice != null) ...[
// DebugButton(
// title: "Get Version",
// method: "Version",
// // func: () async => await btc.getVersion(selectedDevice!),
// func: () async => await ltc.getVersion(selectedDevice!),
// ),
// DebugButton(
// title: "Get Wallet Address",
// method: "Wallet Address",
// func: () async {
// setState(() => counter++);
// final derivationPath = "m/84'/2'/0'/0/0";
// return await ltc.getAccounts(selectedDevice!,
// accountsDerivationPath: derivationPath);
// // return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
// // return await ethereum!.getHardwareWalletAccounts(selectedDevice!);
// },
// ),
// DebugButton(
// title: "Send Money",
// method: "Raw Tx",
// func: sendMoney
// ),
// Padding(
// padding: EdgeInsets.only(top: 20),
// child: PrimaryButton(
// text: "Disconnect",
// onPressed: () => disconnectCurrentDevice(),
// color: Theme.of(context).colorScheme.primary,
// textColor: Theme.of(context).colorScheme.onPrimary),
// ),
// ],
// if (selectedDevice == null) ...[
// ...devices
// .map(
// (device) => Padding(
// padding: EdgeInsets.only(bottom: 20),
// child: DeviceTile(
// onPressed: () {
// setState(() => selectedDevice = device);
// ledger.connect(device);
// },
// title: device.name,
// leading: imageLedger,
// connectionType: device.connectionType,
// ),
// ),
// )
// .toList(),
// PrimaryButton(
// text: "Refresh BLE",
// onPressed: () async {
// setState(() => devices = []);
// ledger.scan().listen((device) => setState(() {
// devices.add(device);
// }));
// },
// color: Theme.of(context).colorScheme.primary,
// textColor: Theme.of(context).colorScheme.onPrimary),
// Padding(
// padding: EdgeInsets.only(top: 20),
// child: PrimaryButton(
// text: "Use USB",
// onPressed: () async {
// final dev = await ledger.listUsbDevices();
// setState(() => devices = dev);
// },
// color: Theme.of(context).colorScheme.primary,
// textColor: Theme.of(context).colorScheme.onPrimary),
// ),
// ],
// ],
// ),
// )),
// );
// }
//
// Future<String> sendMoney() async {
// final readyInputs = [
// LedgerTransaction(
// rawTx: "010000000001018c055c85c3724c98842d27712771dd0de139711f5940bba2df4615c5522184740000000017160014faf7f6dfb4e70798b92c93f33b4c51024491829df0ffffff022b05c70000000000160014f489f947fd13a1fb44ac168427081d3f30b6ce0cde9dd82e0000000017a914d5eca376cb49d65031220ff9093b7d407073ed0d8702483045022100f648c9f6a9b8f35b6ec29bbfae312c95ed3d56ce6a3f177d994efe90562ec4bd02205b82ce2c94bc0c9d152c3afc668b200bd82f48d6a14e83c66ba0f154cd5f69190121038f1dca119420d4aa7ad04af1c0d65304723789cccc56d335b18692390437f35900000000",
// outputIndex: 0,
// ownerPublicKey:
// HexUtils.decode("03b2e67958ed3356e329e05cf94c3bee6b20c17175ac3b2a1278e073bf44f5d6ec"),
// ownerDerivationPath: "m/84'/2'/0'/0/0",
// sequence: 0xffffffff,
// )
// ];
//
// final outputs = [
// BitcoinOutput(
// address: P2wpkhAddress.fromAddress(
// address: "ltc1qn0g5e36xaj07lqj6w9xn52ng07hud42g3jf5ps",
// network: LitecoinNetwork.mainnet),
// value: BigInt.from(1000000)),
// BitcoinOutput(
// address: P2wpkhAddress.fromAddress(
// address: "ltc1qrx29qz4ghu4j0xk37ptgk7034cwpmjyxhrcnk9",
// network: LitecoinNetwork.mainnet),
// value: BigInt.from(12042705)),
// ];
// return await ltc.createTransaction(selectedDevice!,
// inputs: readyInputs,
// outputs: outputs
// .map((e) => TransactionOutput.fromBigInt(
// e.value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
// .toList(),
// sigHashType: 0x01,
// additionals: ["bech32"],
// isSegWit: true,
// useTrustedInputForSegwit: true);
// }
//
// Widget DebugButton(
// {required String title, required String method, required Future<dynamic> Function() func}) {
// return Padding(
// padding: EdgeInsets.only(top: 20),
// child: PrimaryButton(
// text: title,
// onPressed: () async {
// try {
// setState(() => status = "Sending...");
// final acc = await func();
// setState(() => status = "$method: $acc");
// printV("$method: $acc");
// } on LedgerException catch (ex) {
// setState(() => status = "${ex.errorCode.toRadixString(16)} ${ex.message}");
// printV("${ex.errorCode.toRadixString(16)} ${ex.message}");
// }
// },
// color: Theme.of(context).colorScheme.primary,
// textColor: Theme.of(context).colorScheme.onPrimary),
// );
// }
// }
@@ -1,78 +0,0 @@
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class DeviceTile extends StatelessWidget {
const DeviceTile({
required this.onPressed,
required this.title,
this.leading,
this.connectionType,
});
final VoidCallback onPressed;
final String title;
final String? leading;
final HardwareWalletConnectionType? connectionType;
String? get connectionTypeIcon {
switch (connectionType) {
case HardwareWalletConnectionType.ble:
return 'assets/images/bluetooth.png';
case HardwareWalletConnectionType.usb:
return 'assets/images/usb.png';
default:
return null;
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: Container(
width: double.infinity,
padding: EdgeInsets.all(24),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CakeImageWidget(
height: 30,
imageUrl: leading,
colorFilter:
ColorFilter.mode(Theme.of(context).colorScheme.onSurface, BlendMode.srcIn),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
child: Text(
title,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
),
if (connectionTypeIcon != null)
Center(
child: Image.asset(
connectionTypeIcon!,
height: 25,
color: Theme.of(context).colorScheme.onSurface,
),
)
],
),
),
);
}
}
@@ -459,7 +459,7 @@ class _ContactListBodyState extends State<ContactListBody> {
color: Theme.of(context).colorScheme.onSurface,
);
return Padding(
padding: const EdgeInsets.only(bottom: FeatureFlag.hasNewUi ? 48 : 0),
padding: const EdgeInsets.only(bottom: 48),
child: MergeSemantics(
child: SizedBox(
height: 58,
@@ -1,446 +0,0 @@
import 'dart:async';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/page_indicator.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/new_main_navbar_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/widgets/evm_switcher.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/haven_wallet_removal_popup.dart';
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
class DashboardPage extends StatefulWidget {
DashboardPage({
required this.bottomSheetService,
required this.balancePage,
required this.dashboardViewModel,
required this.addressListViewModel,
});
final BalancePage balancePage;
final BottomSheetService bottomSheetService;
final DashboardViewModel dashboardViewModel;
final WalletAddressListViewModel addressListViewModel;
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
@override
void initState() {
super.initState();
bool isMobileLayout =
responsiveLayoutUtil.screenWidth < ResponsiveLayoutUtilBase.kMobileThreshold;
reaction((_) => responsiveLayoutUtil.screenWidth, (screenWidth) {
// Check if it was previously in mobile layout, and now changing to desktop
if (isMobileLayout &&
screenWidth > ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
setState(() {
isMobileLayout = false;
});
}
// Check if it was previously in desktop layout, and now changing to mobile
if (!isMobileLayout &&
screenWidth <= ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
setState(() {
isMobileLayout = true;
});
}
});
}
@override
Widget build(BuildContext context) {
Widget dashboardChild;
final dashboardPageView = RefreshIndicator(
displacement: responsiveLayoutUtil.screenHeight * 0.1,
onRefresh: () async {
unawaited(widget.balancePage.nftViewModel.getNFTAssetByWallet());
await widget.dashboardViewModel.refreshDashboard();
},
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
height: responsiveLayoutUtil.screenHeight,
child: _DashboardPageView(
balancePage: widget.balancePage,
bottomSheetService: widget.bottomSheetService,
dashboardViewModel: widget.dashboardViewModel,
addressListViewModel: widget.addressListViewModel,
),
),
),
);
if (DeviceInfo.instance.isDesktop) {
if (responsiveLayoutUtil.screenWidth >
ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) {
dashboardChild = getIt.get<DesktopSidebarWrapper>();
} else {
dashboardChild = dashboardPageView;
}
} else if (responsiveLayoutUtil.shouldRenderMobileUI) {
dashboardChild = dashboardPageView;
} else {
dashboardChild = getIt.get<DesktopSidebarWrapper>();
}
return Scaffold(body: dashboardChild);
}
}
class _DashboardPageView extends BasePage {
_DashboardPageView({
required this.bottomSheetService,
required this.balancePage,
required this.dashboardViewModel,
required this.addressListViewModel,
});
final BalancePage balancePage;
@override
bool get gradientBackground => true;
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
@override
bool get resizeToAvoidBottomInset => false;
@override
Widget get endDrawer =>
MenuWidget(dashboardViewModel, ValueKey('dashboard_page_drawer_menu_widget_key'));
@override
Widget leading(BuildContext context) {
if (FeatureFlag.isEVMChainSwitcherEnabled &&
dashboardViewModel.isEVMWallet &&
dashboardViewModel.availableChains.isNotEmpty) {
return TextButton(
style: TextButton.styleFrom(
minimumSize: Size(50, 30),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerLeft,
),
onPressed: () => showDialog(
context: context,
builder: (context) => EvmSwitcher(
chains: dashboardViewModel.availableChains,
currentChain: dashboardViewModel.currentChain,
onChainSelected: (chainId) => dashboardViewModel.selectChain(chainId),
hiddenChainIds: dashboardViewModel.settingsStore.evmHiddenChainIds,
onHiddenChanged: (hidden) =>
dashboardViewModel.settingsStore.setEvmHiddenChainIds(hidden),
),
),
child: Container(
child: SvgPicture.asset(
'assets/images/evm_switcher.svg',
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 30,
),
),
);
}
return Observer(
builder: (context) {
return ServicesUpdatesWidget(
key: ValueKey('dashboard_page_services_update_button_key'),
dashboardViewModel.getServicesStatus(),
enabled: dashboardViewModel.isEnabledBulletinAction,
);
},
);
}
@override
Widget middle(BuildContext context) {
return SyncIndicator(
key: ValueKey('dashboard_page_sync_indicator_button_key'),
dashboardViewModel: dashboardViewModel,
onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync),
);
}
@override
Widget trailing(BuildContext context) {
return Container(
alignment: Alignment.centerRight,
width: 42,
child: TextButton(
key: ValueKey('dashboard_page_wallet_menu_button_key'),
onPressed: () => onOpenEndDrawer(),
child: Semantics(
label: S.of(context).wallet_menu,
child: SvgPicture.asset(
'assets/images/menu.svg',
color: Theme.of(context).colorScheme.primary,
),
),
),
);
}
final DashboardViewModel dashboardViewModel;
final BottomSheetService bottomSheetService;
final WalletAddressListViewModel addressListViewModel;
int get initialPage => dashboardViewModel.shouldShowMarketPlaceInDashboard ? 1 : 0;
ObservableList<Widget> pages = ObservableList<Widget>();
bool _isEffectsInstalled = false;
@override
Widget body(BuildContext context) {
final controller = PageController(initialPage: initialPage);
reaction(
(_) => dashboardViewModel.shouldShowMarketPlaceInDashboard,
(bool value) {
if (!dashboardViewModel.shouldShowMarketPlaceInDashboard) {
controller.jumpToPage(0);
}
pages.clear();
_isEffectsInstalled = false;
_setEffects(context);
if (value) {
controller.jumpToPage(1);
} else {
controller.jumpToPage(0);
}
},
);
_setEffects(context);
return SafeArea(
minimum: EdgeInsets.only(bottom: 0),
child: BottomSheetListener(
bottomSheetService: bottomSheetService,
child: Container(
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Observer(
builder: (context) {
return PageView.builder(
key: const ValueKey('dashboard_page_view_key'),
controller: controller,
physics: const BouncingScrollPhysics(),
itemCount: pages.length,
itemBuilder: (context, index) => pages[index],
);
},
),
Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
IgnorePointer(
child: Container(
alignment: Alignment.bottomCenter,
height: 220,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
context.customColors.backgroundGradientColor.withAlpha(5),
context.customColors.backgroundGradientColor.withAlpha(50),
context.customColors.backgroundGradientColor.withAlpha(125),
context.customColors.backgroundGradientColor.withAlpha(150),
context.customColors.backgroundGradientColor.withAlpha(200),
context.customColors.backgroundGradientColor,
context.customColors.backgroundGradientColor,
],
),
),
),
),
Container(
margin: EdgeInsets.only(bottom: 110),
child: Semantics(
container: true,
label: 'Page indicator',
hint:
'Swipe left or right to change page, or double tap buttons below to navigate directly.',
child: ExcludeSemantics(
excluding: false,
child: PageIndicator(
controller: controller,
dashboardViewModel: dashboardViewModel,
),
),
),
),
NewMainNavBar(
dashboardViewModel: dashboardViewModel,
selectedIndex: 0,
onItemTap: (index) {}),
],
),
],
),
),
),
);
}
void _setEffects(BuildContext context) async {
if (_isEffectsInstalled || !context.mounted) {
return;
}
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
pages.add(
Semantics(
label: S.of(context).apps,
child: CakeFeaturesPage(
dashboardViewModel: dashboardViewModel,
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
),
),
);
}
pages.add(Semantics(label: S.of(context).balance_page, child: balancePage));
pages.add(
Semantics(
label: S.of(context).history,
child: TransactionsPage(dashboardViewModel: dashboardViewModel),
),
);
_isEffectsInstalled = true;
_showReleaseNotesPopup(context);
_showVulnerableSeedsPopup(context);
_showHavenPopup(context);
var needToPresentYat = false;
rootKey.currentState?.isInactive.listen(
(inactive) {
if (needToPresentYat) {
Future<void>.delayed(Duration(milliseconds: 500)).then(
(_) {
showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (_) => YatEmojiId(dashboardViewModel.yatStore.emoji),
);
needToPresentYat = false;
},
);
}
},
);
dashboardViewModel.yatStore.emojiIncommingStream.listen(
(String emoji) {
if (!_isEffectsInstalled || emoji.isEmpty) {
return;
}
needToPresentYat = true;
},
);
}
void _showReleaseNotesPopup(BuildContext context) async {
final sharedPrefs = await SharedPreferences.getInstance();
final currentAppVersion =
VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion);
final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion);
final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall);
if (currentAppVersion != lastSeenAppVersion && !isNewInstall!) {
Future<void>.delayed(
Duration(seconds: 1),
() {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return ReleaseNotesScreen(
title: 'Version ${dashboardViewModel.settingsStore.appVersion}',
);
},
);
},
);
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
} else if (isNewInstall!) {
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
}
}
void _showVulnerableSeedsPopup(BuildContext context) async {
final List<String> affectedWalletNames = await dashboardViewModel.checkAffectedWallets();
if (affectedWalletNames.isNotEmpty) {
Future<void>.delayed(
Duration(seconds: 1),
() {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return VulnerableSeedsPopup(affectedWalletNames);
},
);
},
);
}
}
void _showHavenPopup(BuildContext context) async {
final List<String> havenWalletList = await dashboardViewModel.checkForHavenWallets();
if (havenWalletList.isNotEmpty) {
Future<void>.delayed(
Duration(seconds: 1),
() {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return HavenWalletRemovalPopup(havenWalletList);
},
);
},
);
}
}
}
@@ -1,135 +0,0 @@
import 'dart:async';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/router.dart' as Router;
import 'package:shared_preferences/shared_preferences.dart';
class DesktopDashboardPage extends StatelessWidget {
DesktopDashboardPage({
required this.balancePage,
required this.dashboardViewModel,
required this.addressListViewModel,
required this.desktopKey,
});
final BalancePage balancePage;
final DashboardViewModel dashboardViewModel;
final WalletAddressListViewModel addressListViewModel;
final GlobalKey<NavigatorState> desktopKey;
bool _isEffectsInstalled = false;
StreamSubscription<bool>? _onInactiveSub;
@override
Widget build(BuildContext context) {
_setEffects(context);
return Container(
color: Theme.of(context).colorScheme.surface,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 400,
child: balancePage,
),
Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Navigator(
key: desktopKey,
initialRoute: Routes.desktop_actions,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
return [
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
),
),
],
),
);
}
void _setEffects(BuildContext context) async {
if (_isEffectsInstalled) {
return;
}
_isEffectsInstalled = true;
var needToPresentYat = false;
var isInactive = false;
_onInactiveSub = rootKey.currentState!.isInactive.listen((inactive) {
isInactive = inactive;
if (needToPresentYat) {
Future<void>.delayed(Duration(milliseconds: 500)).then((_) {
showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (_) => YatEmojiId(dashboardViewModel.yatStore.emoji));
needToPresentYat = false;
});
}
});
dashboardViewModel.yatStore.emojiIncommingStream.listen((String emoji) {
if (!_isEffectsInstalled || emoji.isEmpty) {
return;
}
needToPresentYat = true;
});
final sharedPrefs = await SharedPreferences.getInstance();
final currentAppVersion =
VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion);
final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion);
final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall);
if (currentAppVersion != lastSeenAppVersion && !isNewInstall!) {
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return ReleaseNotesScreen(
title: 'Version ${dashboardViewModel.settingsStore.appVersion}');
});
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
} else if (isNewInstall!) {
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
}
_showVulnerableSeedsPopup(context);
}
void _showVulnerableSeedsPopup(BuildContext context) async {
final List<String> affectedWalletNames = await dashboardViewModel.checkAffectedWallets();
if (affectedWalletNames.isNotEmpty) {
Future<void>.delayed(
Duration(seconds: 1),
() {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return VulnerableSeedsPopup(affectedWalletNames);
},
);
},
);
}
}
}
@@ -1,71 +0,0 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
class DesktopActionButton extends StatelessWidget {
final String image;
final String title;
final bool canShow;
final bool isEnabled;
final Function() onTap;
const DesktopActionButton({
Key? key,
required this.title,
required this.image,
required this.onTap,
bool? canShow,
bool? isEnabled,
}) : this.isEnabled = isEnabled ?? true,
this.canShow = canShow ?? true,
super(key: key);
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
child: GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(vertical: 25),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
image,
height: 30,
width: 30,
color: isEnabled
? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 10),
AutoSizeText(
title,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 24,
fontWeight: FontWeight.bold,
color: isEnabled
? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
)
],
),
),
),
),
),
);
}
}
@@ -1,87 +0,0 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class DesktopDashboardActions extends StatelessWidget {
final DashboardViewModel dashboardViewModel;
const DesktopDashboardActions(this.dashboardViewModel, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.surface,
child: Observer(builder: (_) {
return Column(
children: [
const SizedBox(height: 16),
DesktopActionButton(
title: MainActions.showWalletsAction.name(context),
image: MainActions.showWalletsAction.image,
canShow: MainActions.showWalletsAction.canShow?.call(dashboardViewModel),
isEnabled: MainActions.showWalletsAction.isEnabled?.call(dashboardViewModel),
onTap: () async =>
await MainActions.showWalletsAction.onTap(context, dashboardViewModel),
),
DesktopActionButton(
title: MainActions.swapAction.name(context),
image: MainActions.swapAction.image,
canShow: MainActions.swapAction.canShow?.call(dashboardViewModel),
isEnabled: MainActions.swapAction.isEnabled?.call(dashboardViewModel),
onTap: () async => await MainActions.swapAction.onTap(context, dashboardViewModel),
),
Row(
children: [
Expanded(
child: DesktopActionButton(
title: MainActions.receiveAction.name(context),
image: MainActions.receiveAction.image,
canShow: MainActions.receiveAction.canShow?.call(dashboardViewModel),
isEnabled: MainActions.receiveAction.isEnabled?.call(dashboardViewModel),
onTap: () async =>
await MainActions.receiveAction.onTap(context, dashboardViewModel),
),
),
Expanded(
child: DesktopActionButton(
title: MainActions.sendAction.name(context),
image: MainActions.sendAction.image,
canShow: MainActions.sendAction.canShow?.call(dashboardViewModel),
isEnabled: MainActions.sendAction.isEnabled?.call(dashboardViewModel),
onTap: () async =>
await MainActions.sendAction.onTap(context, dashboardViewModel),
),
),
],
),
Row(
children: [
Expanded(
child: DesktopActionButton(
title: MainActions.tradeAction.name(context),
image: MainActions.tradeAction.image,
canShow: MainActions.tradeAction.canShow?.call(dashboardViewModel),
isEnabled: MainActions.tradeAction.isEnabled?.call(dashboardViewModel),
onTap: () async =>
await MainActions.tradeAction.onTap(context, dashboardViewModel),
),
),
],
),
Expanded(
child: CakeFeaturesPage(
dashboardViewModel: dashboardViewModel,
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
),
),
],
);
}),
);
}
}
@@ -1,45 +0,0 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/themes/core/theme_store.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class DesktopDashboardNavbar extends StatelessWidget implements ObstructingPreferredSizeWidget {
final Widget leading;
final Widget middle;
final Widget trailing;
DesktopDashboardNavbar({
super.key,
required this.leading,
required this.middle,
required this.trailing,
});
MaterialThemeBase get currentTheme => getIt.get<ThemeStore>().currentTheme;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsetsDirectional.only(end: 24),
color: Theme.of(context).colorScheme.surfaceContainerLowest.withOpacity(0.1),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: leading),
middle,
trailing,
],
),
),
);
}
@override
Size get preferredSize => Size.fromHeight(60);
@override
bool shouldFullyObstruct(BuildContext context) => false;
}
@@ -1,32 +0,0 @@
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart';
import 'package:flutter/material.dart';
class SideMenu extends StatelessWidget {
const SideMenu({
super.key,
required this.topItems,
required this.bottomItems,
required this.width,
});
final List<SideMenuItem> topItems;
final List<SideMenuItem> bottomItems;
final double width;
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.surfaceContainerLowest.withOpacity(0.1),
width: width,
height: MediaQuery.of(context).size.height,
child: Column(
children: [
...topItems,
Spacer(),
...bottomItems,
SizedBox(height: 30),
],
),
);
}
}
@@ -1,58 +0,0 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:flutter/material.dart';
class SideMenuItem extends StatelessWidget {
const SideMenuItem({
Key? key,
required this.onTap,
this.imagePath,
this.icon,
this.widget,
this.isSelected = false,
}) : assert(widget != null || icon != null || imagePath != null);
final void Function() onTap;
final String? imagePath;
final IconData? icon;
final bool isSelected;
final Widget? widget;
Color _setColor(BuildContext context) {
if (isSelected) {
return Theme.of(context).colorScheme.primary;
} else {
return Theme.of(context).colorScheme.onSurfaceVariant;
}
}
@override
Widget build(BuildContext context) {
return InkWell(
child: Padding(
padding: EdgeInsets.all(20),
child: widget ?? _getIcon(context),
),
onTap: () => onTap.call(),
highlightColor: Colors.transparent,
focusColor: Colors.transparent,
hoverColor: Colors.transparent,
splashColor: Colors.transparent,
);
}
Widget _getIcon(BuildContext context) {
return icon != null
? Icon(
icon,
color: _setColor(context),
size: 30,
)
: CakeImageWidget(
imageUrl: imagePath ?? '',
fit: BoxFit.cover,
height: 30,
width: 30,
color: _setColor(context),
);
}
}
@@ -1,173 +0,0 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/router.dart' as Router;
import 'package:mobx/mobx.dart';
class DesktopSidebarWrapper extends BasePage {
final BottomSheetService bottomSheetService;
final Widget child;
final DesktopSidebarViewModel desktopSidebarViewModel;
final DashboardViewModel dashboardViewModel;
final GlobalKey<NavigatorState> desktopNavigatorKey;
DesktopSidebarWrapper({
required this.child,
required this.bottomSheetService,
required this.desktopSidebarViewModel,
required this.dashboardViewModel,
required this.desktopNavigatorKey,
});
@override
ObstructingPreferredSizeWidget appBar(BuildContext context) => DesktopDashboardNavbar(
leading: Padding(
padding: EdgeInsets.only(left: sideMenuWidth),
child: getIt<DesktopWalletSelectionDropDown>(),
),
middle: SyncIndicator(
dashboardViewModel: dashboardViewModel,
onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync),
),
trailing: InkWell(
onTap: () {
Navigator.of(context).pushNamed(
Routes.unlock,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close();
}
},
);
},
child: Icon(Icons.lock_outline),
),
);
@override
bool get resizeToAvoidBottomInset => false;
final pageController = PageController();
double get sideMenuWidth => 76.0;
@override
Widget body(BuildContext context) {
_setEffects();
return BottomSheetListener(
bottomSheetService: bottomSheetService,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Observer(builder: (_) {
return SideMenu(
width: sideMenuWidth,
topItems: [
SideMenuItem(
// imagePath: 'assets/images/wallet_outline.png',
icon: Icons.home,
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.dashboard,
onTap: () {
desktopSidebarViewModel.onPageChange(SidebarItem.dashboard);
desktopNavigatorKey.currentState
?.pushNamedAndRemoveUntil(Routes.desktop_actions, (route) => false);
},
),
SideMenuItem(
onTap: () {
if (desktopSidebarViewModel.currentPage == SidebarItem.transactions) {
desktopNavigatorKey.currentState
?.pushNamedAndRemoveUntil(Routes.desktop_actions, (route) => false);
desktopSidebarViewModel.resetSidebar();
} else {
desktopSidebarViewModel.onPageChange(SidebarItem.transactions);
desktopNavigatorKey.currentState?.pushNamed(Routes.transactionsPage);
}
},
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.transactions,
imagePath: 'assets/images/history.svg',
),
SideMenuItem(
widget: ServicesUpdatesWidget(
dashboardViewModel.getServicesStatus(),
enabled: dashboardViewModel.isEnabledBulletinAction,
),
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.status,
onTap: () {},
),
],
bottomItems: [
SideMenuItem(
imagePath: 'assets/images/support_icon.png',
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.support,
onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.support),
),
SideMenuItem(
imagePath: 'assets/images/settings_outline.png',
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.settings,
onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.settings),
),
],
);
}),
Expanded(
child: PageView(
controller: pageController,
physics: NeverScrollableScrollPhysics(),
children: [
child,
Container(
color: Theme.of(context).colorScheme.surface,
padding: EdgeInsets.all(20),
child: Navigator(
initialRoute: Routes.support,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
return [
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
),
Navigator(
initialRoute: Routes.desktop_settings_page,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
return [
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
],
),
),
],
),
);
}
void _setEffects() async {
reaction<SidebarItem>((_) => desktopSidebarViewModel.currentPage, (page) {
if (page == SidebarItem.dashboard) {
pageController.jumpToPage(0);
return;
}
pageController.jumpToPage(page.index - 1);
});
}
}
@@ -1,285 +0,0 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/desktop_dropdown_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/svg.dart';
class DesktopWalletSelectionDropDown extends StatefulWidget {
final WalletListViewModel walletListViewModel;
final AuthService _authService;
DesktopWalletSelectionDropDown(this.walletListViewModel, this._authService, {Key? key})
: super(key: key);
@override
State<DesktopWalletSelectionDropDown> createState() => _DesktopWalletSelectionDropDownState();
}
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
final moneroIcon =
Image.asset('assets/new-ui/crypto_full_icons/monero.svg', height: 24, width: 24);
final bitcoinIcon =
Image.asset('assets/new-ui/crypto_full_icons/bitcoin.svg', height: 24, width: 24);
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
final litecoinIcon =
Image.asset('assets/new-ui/crypto_full_icons/litecoin.svg', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.webp', height: 24, width: 24);
final ethereumIcon =
Image.asset('assets/new-ui/crypto_full_icons/ethereum.svg', height: 24, width: 24);
final polygonIcon =
Image.asset('assets/new-ui/crypto_full_icons/polygon.svg', height: 24, width: 24);
final bitcoinCashIcon =
Image.asset('assets/new-ui/crypto_full_icons/bitcoin-cash.svg', height: 24, width: 24);
final baseIcon = Image.asset('assets/new-ui/crypto_full_icons/base.svg', height: 24, width: 24);
final arbitrumIcon =
Image.asset('assets/new-ui/crypto_full_icons/arbitrum.svg', height: 24, width: 24);
final bscIcon = Image.asset('assets/new-ui/crypto_full_icons/bnb.svg', height: 24, width: 24);
final nanoIcon = Image.asset('assets/new-ui/crypto_full_icons/nano.svg', height: 24, width: 24);
final bananoIcon = Image.asset('assets/new-ui/crypto_full_icons/nano.svg', height: 24, width: 24);
final solanaIcon =
Image.asset('assets/new-ui/crypto_full_icons/solana.svg', height: 24, width: 24);
final tronIcon = Image.asset('assets/new-ui/crypto_full_icons/tron.svg', height: 24, width: 24);
final wowneroIcon =
Image.asset('assets/new-ui/crypto_full_icons/wownero.svg', height: 24, width: 24);
final zanoIcon = Image.asset('assets/new-ui/crypto_full_icons/zano.svg', height: 24, width: 24);
final decredIcon =
Image.asset('assets/new-ui/crypto_full_icons/decred.svg', height: 24, width: 24);
final dogeIcon =
Image.asset('assets/new-ui/crypto_full_icons/dogecoin.svg', height: 24, width: 24);
final zcashIcon = Image.asset('assets/new-ui/crypto_full_icons/zcash.svg', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
Image _newWalletImage(BuildContext context) => Image.asset(
'assets/images/new_wallet.png',
height: 12,
width: 12,
color: Theme.of(context).colorScheme.onSurface,
);
Image _restoreWalletImage(BuildContext context) => Image.asset(
'assets/images/restore_wallet.png',
height: 12,
width: 12,
color: Theme.of(context).colorScheme.onSurface,
);
Flushbar<void>? _progressBar;
@override
Widget build(BuildContext context) {
final themeData = Theme.of(context);
return Observer(builder: (context) {
final dropDownItems = [
...widget.walletListViewModel.wallets
.map((wallet) => DesktopDropdownItem(
isSelected: wallet.isCurrent,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: DropDownItemWidget(
title: wallet.name,
image: wallet.isEnabled
? _imageFor(type: wallet.type, isTestnet: wallet.isTestnet)
: nonWalletTypeIcon,
),
),
onSelected: () => _onSelectedWallet(wallet),
))
.toList(),
DesktopDropdownItem(
onSelected: () => _navigateToCreateWallet(),
child: DropDownItemWidget(
title: S.of(context).create_new,
image: _newWalletImage(context),
),
),
DesktopDropdownItem(
onSelected: () => _navigateToRestoreWallet(),
child: DropDownItemWidget(
title: S.of(context).restore_wallet,
image: _restoreWalletImage(context),
),
),
];
final selectedItem = dropDownItems.firstWhere(
(element) => element.isSelected,
orElse: () => dropDownItems.first,
);
return DropdownButton<DesktopDropdownItem>(
items: dropDownItems
.map(
(wallet) => DropdownMenuItem<DesktopDropdownItem>(
child: wallet.child,
value: wallet,
),
)
.toList(),
onChanged: (item) {
item?.onSelected();
},
dropdownColor: themeData.colorScheme.surface,
style: themeData.textTheme.bodyMedium,
selectedItemBuilder: (context) => dropDownItems.map((item) => item.child).toList(),
value: selectedItem,
underline: const SizedBox(),
focusColor: Colors.transparent,
borderRadius: BorderRadius.circular(15.0),
);
});
}
void _onSelectedWallet(WalletListItem selectedWallet) async {
if (selectedWallet.isCurrent || !selectedWallet.isEnabled) return;
WidgetsBinding.instance.addPostFrameCallback((_) async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_wallet_alert_title,
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
actionRightButton: () => Navigator.of(dialogContext).pop(true));
}) ??
false;
if (confirmed) {
await _loadWallet(selectedWallet);
}
});
}
Widget _imageFor({required WalletType type, bool? isTestnet}) {
switch (type) {
case WalletType.bitcoin:
if (isTestnet == true) return tBitcoinIcon;
return bitcoinIcon;
case WalletType.monero:
return moneroIcon;
case WalletType.wownero:
return wowneroIcon;
case WalletType.litecoin:
return litecoinIcon;
case WalletType.haven:
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.bitcoinCash:
return bitcoinCashIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
case WalletType.polygon:
return polygonIcon;
case WalletType.solana:
return solanaIcon;
case WalletType.tron:
return tronIcon;
case WalletType.zano:
return zanoIcon;
case WalletType.decred:
return decredIcon;
case WalletType.dogecoin:
return dogeIcon;
case WalletType.base:
return baseIcon;
case WalletType.arbitrum:
return arbitrumIcon;
case WalletType.bsc:
return bscIcon;
case WalletType.zcash:
return zcashIcon;
case WalletType.none:
return nonWalletTypeIcon;
}
}
Future<void> _loadWallet(WalletListItem wallet) async {
if (SettingsStoreBase.walletPasswordDirectInput) {
Navigator.of(context).pushNamed(Routes.walletUnlockLoadable,
arguments: WalletUnlockArguments(
callback: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (isAuthenticatedSuccessfully) {
auth.close();
setState(() {});
}
},
walletName: wallet.name,
walletType: wallet.type));
return;
}
widget._authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) return;
try {
if (mounted) {
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
}
await widget.walletListViewModel.loadWallet(wallet);
hideProgressText();
setState(() {});
} catch (e) {
if (mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
}
},
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
);
}
void _navigateToCreateWallet() {
if (isSingleCoin) {
widget._authService.authenticateAction(
context,
route: Routes.newWallet,
arguments: NewWalletArguments(type: widget.walletListViewModel.currentWalletType),
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
widget._authService.authenticateAction(
context,
route: Routes.newWalletType,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
}
}
void _navigateToRestoreWallet() {
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
}
void changeProcessText(String text) {
_progressBar = createBar<void>(text, context, duration: null)..show(context);
}
void hideProgressText() {
_progressBar?.dismiss();
_progressBar = null;
}
}
@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
class DropDownItemWidget extends StatelessWidget {
const DropDownItemWidget({super.key, required this.title, required this.image});
final double tileHeight = 60;
final Widget image;
final String title;
@override
Widget build(BuildContext context) {
return Container(
height: tileHeight,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
image,
SizedBox(width: 10),
Flexible(
child: Text(
title,
style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
)
],
),
);
}
}
@@ -1,330 +0,0 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AddressPage extends BasePage {
AddressPage({
required this.addressListViewModel,
required this.dashboardViewModel,
required this.receiveOptionViewModel,
}) : _cryptoAmountFocus = FocusNode(),
_formKey = GlobalKey<FormState>(),
_amountController = TextEditingController() {
_amountController.addListener(() {
if (_formKey.currentState!.validate()) {
addressListViewModel.changeAmount(_amountController.text);
}
});
}
final WalletAddressListViewModel addressListViewModel;
final DashboardViewModel dashboardViewModel;
final ReceiveOptionViewModel receiveOptionViewModel;
final TextEditingController _amountController;
final GlobalKey<FormState> _formKey;
final FocusNode _cryptoAmountFocus;
@override
bool get gradientBackground => true;
@override
bool get resizeToAvoidBottomInset => false;
bool effectsInstalled = false;
@override
Widget? leading(BuildContext context) {
final _backButton = Icon(
Icons.arrow_back_ios,
color: Theme.of(context).colorScheme.primary,
size: 16,
);
final _closeButton = currentTheme.isDark ? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
return MergeSemantics(
child: SizedBox(
height: isMobileView ? 37 : 45,
width: isMobileView ? 37 : 45,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
child: TextButton(
style: ButtonStyle(
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent),
),
onPressed: () => onClose(context),
child: !isMobileView ? _closeButton : _backButton,
),
),
),
),
);
}
@override
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
color: titleColor(context), receiveOptionViewModel: receiveOptionViewModel);
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
@override
Widget? trailing(BuildContext context) {
return Material(
color: Colors.transparent,
child: Semantics(
label: S.of(context).share,
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () {
ShareUtil.share(
text: addressListViewModel.uri.toString(),
context: context,
);
},
icon: Icon(Icons.share, size: 20, color: Theme.of(context).colorScheme.primary),
),
),
);
}
@override
Widget body(BuildContext context) {
_setEffects(context);
return KeyboardActions(
autoScroll: false,
disableScroll: true,
tapOutsideBehavior: TapOutsideBehavior.translucentDismiss,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).colorScheme.surface,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
],
),
child: Container(
padding: EdgeInsets.fromLTRB(24, 0, 24, 32),
child: Column(
children: <Widget>[
Expanded(
child: QRWidget(
formKey: _formKey,
addressListViewModel: addressListViewModel,
amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController,
),
),
SizedBox(height: 16),
Observer(
builder: (_) {
if (addressListViewModel.hasAddressList) {
return SelectButton(
text: addressListViewModel.buttonTitle,
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
color: Theme.of(context).colorScheme.surfaceContainer,
arrowColor: Theme.of(context).colorScheme.onSurfaceVariant,
textSize: 14,
height: 50,
);
} else {
return const SizedBox();
}
},
),
if (addressListViewModel.hasTokensList) ...[
Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).colorScheme.surfaceContainer),
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(18),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CakeImageWidget(
imageUrl: addressListViewModel.monoImage,
height: 16,
width: 16,
color: Theme.of(context).colorScheme.onSurface,
),
SizedBox(width: 10),
Text(
'${S.current.your} ${addressListViewModel.walletTypeName} ${S.current.address}',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
],
),
SizedBox(height: 10),
Text(
'${S.current.qr_instruction} ${addressListViewModel.walletTypeName}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 20),
Observer(
builder: (_) {
final walletImages = addressListViewModel
.getWalletImages(addressListViewModel.selectedChainId);
return Center(
child: SizedBox(
height: 40,
width: walletImages.length * 32.0,
child: Stack(
children: [
for (int i = walletImages.length - 1; i >= 0; i--)
Positioned(
left: i * 25.0,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.surfaceContainer,
width: 3,
),
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(24),
),
child: ClipOval(
child: CakeImageWidget(
height: 35,
width: 35,
imageUrl: walletImages[i],
color: walletImages.last == walletImages[i]
? Theme.of(context).colorScheme.onSurfaceVariant
: null,
),
),
),
),
],
),
),
);
},
),
],
),
),
SizedBox(height: 48),
],
],
),
),
);
}
void _setEffects(BuildContext context) {
if (effectsInstalled) {
return;
}
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
if (dashboardViewModel.type == WalletType.bitcoin &&
bitcoin!.isBitcoinReceivePageOption(option)) {
addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
return;
}
if (dashboardViewModel.type == WalletType.zcash) {
addressListViewModel.setAddressType(zcash!.getOptionToType(option));
return;
}
switch (option) {
case ReceivePageOption.anonPayInvoice:
Navigator.pushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [addressListViewModel.address.address, option],
);
break;
case ReceivePageOption.anonPayDonationLink:
final sharedPreferences = getIt.get<SharedPreferences>();
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
final donationWalletName =
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
if (clearnetUrl != null &&
onionUrl != null &&
addressListViewModel.wallet.name == donationWalletName) {
Navigator.pushNamed(
context,
Routes.anonPayReceivePage,
arguments: AnonPayReceivePageArgs(
invoiceInfo: AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl,
onionUrl: onionUrl,
address: addressListViewModel.address.address,
),
qrImage: addressListViewModel.qrImage,
),
);
} else {
Navigator.pushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [addressListViewModel.address.address, option],
);
}
break;
default:
if ([WalletType.bitcoin, WalletType.litecoin].contains(addressListViewModel.type)) {
addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
}
if (addressListViewModel.type == WalletType.zcash) {
printV("help me i'll kms if that wont work: ${zcash!.getZcashAddressType(option)}");
addressListViewModel.setAddressType(zcash!.getZcashAddressType(option));
}
}
});
effectsInstalled = true;
}
}
@@ -1,81 +0,0 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class BalancePage extends StatelessWidget {
BalancePage({
required this.dashboardViewModel,
required this.settingsStore,
required this.nftViewModel,
});
final DashboardViewModel dashboardViewModel;
final NFTViewModel nftViewModel;
final SettingsStore settingsStore;
@override
Widget build(BuildContext context) {
return Observer(
builder: (context) {
final isNFTActivated = isNFTACtivatedChain(dashboardViewModel.type,
chainId: dashboardViewModel.wallet.chainId);
return DefaultTabController(
key: ValueKey<bool>(isNFTActivated),
length: isNFTActivated ? 2 : 1,
child: Column(
children: [
if (isNFTActivated)
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: TabBar(
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
physics: const NeverScrollableScrollPhysics(),
labelStyle: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
height: 1,
color: Theme.of(context).colorScheme.primary,
),
unselectedLabelStyle: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
height: 1,
color: Theme.of(context).colorScheme.primary,
),
labelColor: Theme.of(context).colorScheme.primary,
dividerColor: Colors.transparent,
indicatorColor: Theme.of(context).colorScheme.primary,
unselectedLabelColor:
Theme.of(context).colorScheme.secondary.withOpacity(0.7),
tabAlignment: TabAlignment.start,
tabs: const [
Tab(text: 'My Crypto'),
Tab(text: 'My NFTs'),
],
),
),
),
Expanded(
child: TabBarView(
physics: const NeverScrollableScrollPhysics(),
children: [
CryptoBalanceWidget(dashboardViewModel: dashboardViewModel),
if (isNFTActivated) NFTListingPage(nftViewModel: nftViewModel)
],
),
),
],
),
);
},
);
}
}
@@ -1,693 +0,0 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:url_launcher/url_launcher.dart';
class BalanceRowWidget extends StatelessWidget {
BalanceRowWidget({
required this.availableBalanceLabel,
required this.availableBalance,
required this.availableFiatBalance,
required this.additionalBalanceLabel,
required this.additionalBalance,
required this.additionalFiatBalance,
required this.secondAvailableBalanceLabel,
required this.secondAvailableBalance,
required this.secondAvailableFiatBalance,
required this.secondAdditionalBalanceLabel,
required this.secondAdditionalBalance,
required this.secondAdditionalFiatBalance,
required this.frozenBalance,
required this.frozenFiatBalance,
required this.currency,
required this.hasAdditionalBalance,
required this.hasSecondAvailableBalance,
required this.hasSecondAdditionalBalance,
required this.isTestnet,
required this.dashboardViewModel,
super.key,
});
final String availableBalanceLabel;
final String availableBalance;
final String availableFiatBalance;
final String additionalBalanceLabel;
final String additionalBalance;
final String additionalFiatBalance;
final String secondAvailableBalanceLabel;
final String secondAvailableBalance;
final String secondAvailableFiatBalance;
final String secondAdditionalBalanceLabel;
final String secondAdditionalBalance;
final String secondAdditionalFiatBalance;
final String frozenBalance;
final String frozenFiatBalance;
final CryptoCurrency currency;
final bool hasAdditionalBalance;
final bool hasSecondAvailableBalance;
final bool hasSecondAdditionalBalance;
final bool isTestnet;
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
gradient: LinearGradient(
colors: [
context.customColors.cardGradientColorPrimary,
context.customColors.cardGradientColorSecondary,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: TextButton(
onPressed: _showToast,
onLongPress: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
style: TextButton.styleFrom(
side: BorderSide(
width: 1.25, color: Theme.of(context).colorScheme.surfaceContainerHigh),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
),
child: Container(
margin: const EdgeInsets.only(
top: 10,
left: 12,
right: 12,
bottom: 10,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: hasAdditionalBalance
? () => _showBalanceDescription(
context, S.of(context).available_balance_description)
: null,
child: Row(
children: [
Semantics(
hint: 'Double tap to see more information',
container: true,
child: Text(
'${availableBalanceLabel}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
),
if (hasAdditionalBalance)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(
Icons.help_outline,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
SizedBox(height: 6),
AutoSizeText(
availableBalance,
minFontSize: 16,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w900,
fontSize: 24,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.start,
),
SizedBox(height: 6),
if (isTestnet)
Text(
S.of(context).testnet_coins_no_value,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
height: 1,
),
),
if (!isTestnet)
Text(
'${availableFiatBalance}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
],
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
errorWidget: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainer,
),
),
),
const SizedBox(height: 10),
Text(
currency.title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
),
],
),
),
],
),
if (currency.isPotentialScam)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
margin: const EdgeInsets.only(top: 4),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.errorContainer,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.warning_amber_outlined,
size: 16,
color: Theme.of(context).colorScheme.onErrorContainer,
),
const SizedBox(width: 4),
Text(
S.of(context).potential_scam,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onErrorContainer,
fontWeight: FontWeight.w700,
),
),
],
),
),
if (frozenBalance.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 26),
Row(
children: [
Text(
S.of(context).frozen_balance,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
],
),
const SizedBox(height: 8),
AutoSizeText(
frozenBalance,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 20,
color: Theme.of(context).colorScheme.primary,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
if (!isTestnet)
Text(
frozenFiatBalance,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(height: 1),
),
],
),
if (hasAdditionalBalance)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 24),
Text(
'${additionalBalanceLabel}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
const SizedBox(height: 8),
AutoSizeText(
additionalBalance,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 20,
color: Theme.of(context).colorScheme.secondary,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
if (!isTestnet)
Text(
'${additionalFiatBalance}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(height: 1),
),
],
),
],
),
),
),
),
if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[
SizedBox(height: 10),
Container(
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
context.customColors.cardGradientColorPrimary,
context.customColors.cardGradientColorSecondary,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: TextButton(
onPressed: _showToast,
onLongPress: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 10, left: 12, right: 8, bottom: 10),
child: Stack(
children: [
if (currency == CryptoCurrency.ltc)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
children: [
ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
size: 40,
),
const SizedBox(height: 10),
Text(
'MWEB',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
),
],
),
],
),
if (currency == CryptoCurrency.btc)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
children: [
SvgPicture.asset(
'assets/images/lightning-icon.svg',
width: 40,
height: 40,
),
const SizedBox(height: 10),
Text(
'Lightning',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
),
],
),
],
),
if (hasSecondAvailableBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onPressedHelp,
child: Row(
children: [
Text(
secondAvailableBalanceLabel,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color:
Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(
Icons.help_outline,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
),
SizedBox(height: 8),
AutoSizeText(
secondAvailableBalance,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontSize: 24,
fontWeight: FontWeight.w800,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 6),
if (!isTestnet)
Text(
secondAvailableFiatBalance,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
],
),
],
),
],
),
),
Container(
margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (hasSecondAdditionalBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
secondAdditionalBalanceLabel,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAdditionalBalance,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontSize: 20,
color: Theme.of(context).colorScheme.secondary,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${secondAdditionalFiatBalance}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
height: 1,
),
),
],
),
],
),
],
),
),
IntrinsicHeight(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Semantics(
label: depositToL2Label,
child: OutlinedButton(
onPressed: () => depositToL2(context),
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
side: BorderSide(
color:
Theme.of(context).colorScheme.outlineVariant.withAlpha(0),
width: 0,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/received.png',
color: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(width: 8),
Text(
depositToL2Label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
),
),
SizedBox(width: 16),
Expanded(
child: Semantics(
label: withdrawFromL2Label,
child: OutlinedButton(
onPressed: () => withdrawFromL2(context),
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surface,
side: BorderSide(
color:
Theme.of(context).colorScheme.outlineVariant.withAlpha(0),
width: 0,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/upload.png',
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
const SizedBox(width: 8),
Text(
withdrawFromL2Label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
),
),
],
),
),
),
const SizedBox(height: 16),
],
),
),
),
],
],
);
}
String get depositToL2Label => dashboardViewModel.type == WalletType.litecoin
? S.current.litecoin_mweb_pegin
: S.current.bitcoin_lightning_deposit;
String get withdrawFromL2Label => dashboardViewModel.type == WalletType.litecoin
? S.current.litecoin_mweb_pegout
: S.current.bitcoin_lightning_withdraw;
Future<void> depositToL2(BuildContext context) async {
PaymentRequest? paymentRequest = null;
if (dashboardViewModel.type == WalletType.litecoin) {
final depositAddress = bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
if ((depositAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$depositAddress"));
}
} else if (dashboardViewModel.type == WalletType.bitcoin) {
final depositAddress = await bitcoin!.getUnusedSpakDepositAddress(dashboardViewModel.wallet);
if ((depositAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("bitcoin:$depositAddress"));
}
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
},
);
}
Future<void> withdrawFromL2(BuildContext context) async {
PaymentRequest? paymentRequest = null;
UnspentCoinType unspentCoinType = UnspentCoinType.any;
final withdrawAddress = bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
if (dashboardViewModel.type == WalletType.litecoin) {
if ((withdrawAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$withdrawAddress"));
}
unspentCoinType = UnspentCoinType.mweb;
} else if (dashboardViewModel.type == WalletType.bitcoin) {
if ((withdrawAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("bitcoin:$withdrawAddress"));
}
unspentCoinType = UnspentCoinType.lightning;
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': unspentCoinType,
},
);
}
void onPressedHelp() {
var helpUri = Uri.parse("https://docs.cakewallet.com/cryptos/bitcoin#lightning");
if (dashboardViewModel.type == WalletType.litecoin) {
helpUri = Uri.parse("https://docs.cakewallet.com/cryptos/litecoin#mweb");
}
launchUrl(helpUri, mode: LaunchMode.externalApplication);
}
void _showBalanceDescription(BuildContext context, String content) {
showPopUp<void>(context: context, builder: (_) => InformationPage(information: content));
}
void _showToast() async {
try {
await Fluttertoast.showToast(
msg: S.current.show_balance_toast,
backgroundColor: Color.fromRGBO(0, 0, 0, 0.85),
);
} catch (_) {}
}
}
@@ -1,479 +0,0 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_row_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/info_card.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/src/widgets/introducing_card.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:url_launcher/url_launcher.dart';
class CryptoBalanceWidget extends StatelessWidget {
const CryptoBalanceWidget({
super.key,
required this.dashboardViewModel,
});
final DashboardViewModel dashboardViewModel;
final btcLockLight = 'assets/images/btc_lock_light.png';
final btcLockDark = 'assets/images/btc_lock_dark.png';
String? get hardwareWalletIcon {
switch (dashboardViewModel.wallet.hardwareWalletType) {
case null:
return null;
case HardwareWalletType.bitbox:
return "assets/new-ui/hardware_wallets/device_bitbox.svg";
case HardwareWalletType.ledger:
return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
case HardwareWalletType.trezor:
return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
case HardwareWalletType.cupcake:
return "assets/images/cupcake.svg";
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
case HardwareWalletType.keystone:
return "assets/images/hardware_wallet/device_qr.svg";
}
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Observer(
builder: (_) {
if (dashboardViewModel.getMoneroError != null) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: DashBoardRoundedCardWidget(
title: "Invalid monero bindings",
subTitle: dashboardViewModel.getMoneroError.toString(),
),
);
}
return Container();
},
),
Observer(
builder: (_) {
if (dashboardViewModel.getWowneroError != null) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: DashBoardRoundedCardWidget(
title: "Invalid wownero bindings",
subTitle: dashboardViewModel.getWowneroError.toString(),
),
);
}
return Container();
},
),
Observer(
builder: (_) => dashboardViewModel.balanceViewModel.hasAccounts
? HomeScreenAccountWidget(
walletName: dashboardViewModel.name, accountName: dashboardViewModel.subname)
: Column(
children: [
SizedBox(height: 16),
Container(
margin: const EdgeInsets.only(left: 24, bottom: 16),
child: Observer(
builder: (_) {
return Row(
children: [
Text(
dashboardViewModel.balanceViewModel.asset,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
if (hardwareWalletIcon != null)
Container(
child: SvgPicture.asset(
hardwareWalletIcon!,
width: 24,
color: Theme.of(context).colorScheme.onSurface,
),
),
if (dashboardViewModel
.balanceViewModel.isHomeScreenSettingsEnabled)
TextButton(
style: TextButton.styleFrom(
minimumSize: Size(50, 30),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerLeft),
onPressed: () => Navigator.pushNamed(
context,
Routes.homeSettings,
arguments: dashboardViewModel.balanceViewModel,
),
child: Container(
child: SvgPicture.asset(
'assets/images/home_screen_setting_icon.svg',
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 30),
),
),
],
);
},
),
),
],
)),
Observer(
builder: (_) {
if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) {
return IntroducingCard(
title: S.of(context).introducing_cake_pay,
subTitle: S.of(context).cake_pay_learn_more,
borderColor: Theme.of(context).colorScheme.outline,
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
}
return Container();
},
),
Observer(builder: (_) {
if (!dashboardViewModel.showRepWarning) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
title: S.of(context).rep_warning,
subTitle: S.of(context).rep_warning_sub,
onTap: () => Navigator.of(context).pushNamed(Routes.changeRep),
onClose: () {
dashboardViewModel.settingsStore.shouldShowRepWarning = false;
},
),
);
}),
Observer(
builder: (_) {
if (dashboardViewModel.balanceViewModel.formattedBalances.isEmpty) {
return Center(
child: Container(
child: Text(
'Loading balances...',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
textAlign: TextAlign.center,
),
),
);
}
return ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 10)),
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
itemBuilder: (__, index) {
final balance =
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
return Observer(builder: (_) {
return BalanceRowWidget(
dashboardViewModel: dashboardViewModel,
availableBalanceLabel:
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
availableBalance: balance.availableBalance,
availableFiatBalance: balance.fiatAvailableBalance,
additionalBalanceLabel:
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
additionalBalance: balance.additionalBalance,
additionalFiatBalance: balance.fiatAdditionalBalance,
frozenBalance: balance.frozenBalance,
frozenFiatBalance: balance.fiatFrozenBalance,
currency: balance.asset,
hasAdditionalBalance:
dashboardViewModel.balanceViewModel.hasAdditionalBalance(balance.asset),
hasSecondAdditionalBalance:
dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance,
hasSecondAvailableBalance:
dashboardViewModel.balanceViewModel.hasSecondAvailableBalance,
secondAdditionalBalance: balance.secondAdditionalBalance,
secondAdditionalFiatBalance: balance.fiatSecondAdditionalBalance,
secondAvailableBalance: balance.secondAvailableBalance,
secondAvailableFiatBalance: balance.fiatSecondAvailableBalance,
secondAdditionalBalanceLabel:
'${dashboardViewModel.balanceViewModel.secondAdditionalBalanceLabel}',
secondAvailableBalanceLabel:
'${dashboardViewModel.balanceViewModel.secondAvailableBalanceLabel}',
isTestnet: dashboardViewModel.isTestnet,
);
});
},
);
},
),
Observer(builder: (context) {
return Column(
children: [
if (dashboardViewModel.isMoneroWalletBrokenReasons.isNotEmpty) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: "This wallet has encountered an issue",
subTitle: "Here are the things that you should note:\n - " +
dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ") +
"\n\nPlease restart your wallet and if it doesn't help contact our support.",
))
],
if (dashboardViewModel.showSilentPaymentsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
child: DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
marginV: 0,
marginH: 0,
customBorder: 30,
title: S.of(context).silent_payments,
subTitle: S.of(context).enable_silent_payments_scanning,
hint: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://docs.cakewallet.com/cryptos/bitcoin#silent-payments"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
S.of(context).what_is_silent_payments,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(
Icons.help_outline,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.silentPaymentsScanningActive,
onTapped: () => _toggleSilentPaymentsScanning(context),
),
)
],
),
],
),
onTap: () => _toggleSilentPaymentsScanning(context),
image: !context.currentTheme.isDark
? Image.asset(btcLockLight, height: 48)
: Image.asset(btcLockDark, height: 48),
),
),
],
if (dashboardViewModel.showMwebCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: InfoCard(
title: S.of(context).litecoin_mweb,
description: S.of(context).litecoin_mweb_description,
leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
rightButtonTitle: S.of(context).enable,
image: 'assets/images/mweb_logo.png',
leftButtonAction: () => _dismissMweb(context),
rightButtonAction: () => _enableMweb(context),
hintWidget: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
mode: LaunchMode.externalApplication,
),
child: Text(
S.of(context).learn_more,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
softWrap: true,
),
),
),
),
],
if (dashboardViewModel.showDecredInfoCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: InfoCard(
title: S.of(context).synchronizing,
description: S.of(context).decred_info_card_details,
image: 'assets/new-ui/crypto_full_icons/decred.svg',
leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
rightButtonTitle: S.of(context).learn_more,
leftButtonAction: () => dashboardViewModel.dismissDecredInfoCard(),
rightButtonAction: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/decred/#spv-sync")),
),
),
],
if (dashboardViewModel.showPayjoinCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: InfoCard(
title: "Payjoin",
description: S.of(context).payjoin_card_content,
hintWidget: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/bitcoin/#payjoin"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
S.of(context).what_is_payjoin,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(
Icons.help_outline,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
),
image: 'assets/images/payjoin.png',
leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
rightButtonTitle: S.of(context).enable,
leftButtonAction: () => dashboardViewModel.dismissPayjoin(),
rightButtonAction: () => _enablePayjoin(context),
),
),
],
],
);
}),
SizedBox(height: 130),
],
),
);
}
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive;
dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive &&
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
));
}
return dashboardViewModel.setSilentPaymentsScanning(newValue);
}
void _enablePayjoin(BuildContext context) {
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).payjoin_enabling_popup_title,
alertContent: S.of(context).payjoin_enabling_popup_content,
buttonText: S.of(context).ok,
buttonAction: () {
Navigator.of(context).pop();
},
));
dashboardViewModel.enablePayjoin();
}
Future<void> _enableMweb(BuildContext context) async {
if (!dashboardViewModel.hasEnabledMwebBefore) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).litecoin_mweb_warning,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
}
dashboardViewModel.setMwebEnabled();
}
Future<void> _dismissMweb(BuildContext context) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).litecoin_mweb_enable_later,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
dashboardViewModel.dismissMweb();
}
}
@@ -8,7 +8,6 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
@@ -18,10 +17,9 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
class CakeFeaturesPage extends StatelessWidget {
CakeFeaturesPage({required this.dashboardViewModel, required this.cakeFeaturesViewModel});
CakeFeaturesPage({required this.dashboardViewModel});
final DashboardViewModel dashboardViewModel;
final CakeFeaturesViewModel cakeFeaturesViewModel;
@override
Widget build(BuildContext context) {
@@ -38,7 +36,7 @@ class CakeFeaturesPage extends StatelessWidget {
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: !FeatureFlag.hasNewUi ? _buildOldUi(context) : _buildNewUi(context),
child: _buildNewUi(context),
),
),
);
@@ -52,19 +50,6 @@ class CakeFeaturesPage extends StatelessWidget {
}
Widget _buildHeader(BuildContext context) {
if (!FeatureFlag.hasNewUi) {
return Padding(
padding: const EdgeInsets.only(left: 24, top: 16, bottom: 16),
child: Text(
S.of(context).apps,
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
);
}
return Align(
alignment: Alignment.center,
child: Padding(
@@ -81,45 +66,6 @@ class CakeFeaturesPage extends StatelessWidget {
);
}
Widget _buildOldUi(BuildContext context) {
return Column(
children: [
const SizedBox(height: 2),
DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => _onCakePayTap(context),
title: 'Cake Pay',
subTitle: S.of(context).cake_pay_subtitle,
image: Image.asset('assets/images/cakepay.png', height: 74, width: 70, fit: BoxFit.cover),
),
Observer(builder: (_) {
if (dashboardViewModel.type == WalletType.ethereum) {
return DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => Navigator.of(context).pushNamed(Routes.dEuroSavings),
title: S.of(context).deuro_savings,
subTitle: S.of(context).deuro_savings_subtitle,
image: Image.asset('assets/images/deuro_icon.png',
height: 80, width: 80, fit: BoxFit.cover),
);
}
return const SizedBox();
}),
DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => _launchUrl("cake.nano-gpt.com"),
title: "NanoGPT",
subTitle: S.of(context).nanogpt_subtitle,
image: Image.asset('assets/images/nanogpt.png', height: 80, width: 80, fit: BoxFit.cover),
),
const Spacer(),
const SizedBox(height: 125),
],
);
}
Widget _buildNewUi(BuildContext context) {
return Column(
@@ -1,83 +0,0 @@
import 'dart:ui';
import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class NavigationDock extends StatelessWidget {
const NavigationDock({
required this.dashboardViewModel,
});
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return Positioned(
child: Observer(
builder: (_) {
return Container(
height: 84,
alignment: Alignment.bottomCenter,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
),
margin: const EdgeInsets.only(left: 8, right: 8, bottom: 16),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
color: Theme.of(context).colorScheme.surfaceContainer,
),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ClipRect(
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: MainActions.all
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
.map(
(action) => Expanded(
child: Semantics(
button: true,
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
child: ActionButton(
key: action.key,
image: Image.asset(
action.image,
height: 24,
width: 24,
color: Theme.of(context).colorScheme.primary,
),
title: action.name(context),
onClick: (action.isEnabled?.call(dashboardViewModel) ?? true)
? () async =>
await action.onTap(context, dashboardViewModel)
: null,
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
? null
: Theme.of(context).colorScheme.onSurface,
),
),
),
)
.toList(),
),
),
),
),
),
),
);
},
),
);
}
}
@@ -1,250 +0,0 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/csv_export_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/order/order_source_description.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/payjoin_transaction_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/header_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/date_section_raw.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transaction_raw.dart';
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
import 'package:intl/intl.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart';
class TransactionsPage extends StatelessWidget {
TransactionsPage({required this.dashboardViewModel});
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
!dashboardViewModel.balanceViewModel.isReversing,
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
!dashboardViewModel.balanceViewModel.isReversing,
child: Container(
color: responsiveLayoutUtil.shouldRenderMobileUI
? null
: Theme.of(context).colorScheme.surface,
child: Column(
children: <Widget>[
Observer(builder: (_) {
final status = dashboardViewModel.status;
if (status is SyncingSyncStatus) {
return DashBoardRoundedCardWidget(
key: ValueKey('transactions_page_syncing_alert_card_key'),
onTap: () {
try {
final uri = Uri.parse("https://docs.cakewallet.com/faq/funds-not-appearing");
launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (_) {}
},
title: S.of(context).syncing_wallet_alert_title,
subTitle: S.of(context).syncing_wallet_alert_content,
);
} else {
return Container();
}
}),
HeaderRow(
dashboardViewModel: dashboardViewModel,
onExportCsv: () =>
getIt.get<CsvExportService>().exportToCsv(dashboardViewModel.items, context),
key: ValueKey('transactions_page_header_row_key'),
),
Expanded(
child: Observer(
builder: (_) {
final items = dashboardViewModel.items;
return items.isNotEmpty
? ListView.builder(
key: ValueKey('transactions_page_list_view_builder_key'),
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) return SizedBox(height: 150);
final item = items[index];
if (item is DateSectionItem) {
return DateSectionRaw(date: item.date, key: item.key);
}
if (item is TransactionListItem) {
if (item.hasTokens && item.assetOfTransaction == null) {
return Container();
}
final transaction = item.transaction;
final transactionType =
dashboardViewModel.getTransactionType(transaction);
List<String> tags = [];
if (dashboardViewModel.type == WalletType.bitcoin) {
if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
tags.add(S.of(context).silent_payment);
}
}
if (dashboardViewModel.type == WalletType.litecoin) {
if (bitcoin!.txIsMweb(transaction)) {
tags.add("MWEB");
}
}
return Observer(
builder: (_) => TransactionRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails, arguments: transaction),
isShield: transaction.additionalInfo['isAutoShield'] == true,
direction: transaction.direction,
formattedDate: DateFormat('HH:mm').format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
formattedFiatAmount:
dashboardViewModel.balanceViewModel.isFiatDisabled
? ''
: item.formattedFiatAmount,
title:
item.formattedTitle + item.formattedStatus + transactionType,
tags: tags,
),
);
}
if (item is AnonpayTransactionListItem) {
final transactionInfo = item.transaction;
return AnonpayTransactionRow(
key: item.key,
onTap: () => Navigator.of(context).pushNamed(
Routes.anonPayDetailsPage,
arguments: transactionInfo),
currency: transactionInfo.fiatAmount != null
? transactionInfo.fiatEquiv ?? ''
: CryptoCurrency.fromFullName(transactionInfo.coinTo)
.name
.toUpperCase(),
provider: transactionInfo.provider,
amount: transactionInfo.fiatAmount?.toString() ??
(transactionInfo.amountTo?.toString() ?? ''),
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
);
}
if (item is PayjoinTransactionListItem) {
final session = item.session;
return PayjoinTransactionRow(
key: item.key,
onTap: () => Navigator.of(context).pushNamed(
Routes.payjoinDetails,
arguments: [item.sessionId, item.transaction],
),
currency: "BTC",
state: item.status,
amount: dashboardViewModel.appStore.amountParsingProxy
.getDisplayCryptoString(
session.amount.toInt(), CryptoCurrency.btc),
createdAt: DateFormat('HH:mm').format(session.inProgressSince!),
isSending: session.isSenderSession,
);
}
if (item is TradeListItem) {
final trade = item.trade;
final tradeFrom = trade.from;
final tradeTo = trade.to;
return tradeFrom != null && tradeTo != null
? Observer(
builder: (_) => TradeRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
swapState: trade.state,
provider: trade.provider,
title: "$tradeFrom → $tradeTo",
fromSymbol: dashboardViewModel.appStore.amountParsingProxy
.getCryptoSymbol(tradeFrom),
toSymbol: dashboardViewModel.appStore.amountParsingProxy
.getCryptoSymbol(tradeTo),
createdAtFormattedDate: trade.createdAt != null
? DateFormat("HH:mm").format(trade.createdAt!)
: null,
formattedAmount: item.tradeFormattedAmount,
formattedReceiveAmount: item.tradeFormattedReceiveAmount,
),
)
: Container();
}
if (item is OrderListItem) {
final order = item.order;
return Observer(
builder: (_) {
// TODO: Refactor Amount Hiding Logic it is not working properly for Orders and Trades
final hide = dashboardViewModel.balanceViewModel.displayMode ==
BalanceDisplayMode.hiddenBalance;
final formattedAmount = hide ? '---' : order.amountFormatted();
final formattedReceiveAmount = hide ? '---' : order.receiveAmount;
return OrderRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.orderDetails, arguments: order),
providerTitle: order.providerTitle,
providerIconPath: order.providerIcon,
from: order.from ?? '',
to: order.to ?? '',
createdAtFormattedDate:
DateFormat('HH:mm').format(order.createdAt),
formattedAmount: formattedAmount,
formattedReceiveAmount:
dashboardViewModel.balanceViewModel.isFiatDisabled &&
order.source == OrderSourceDescription.order
? ''
: formattedReceiveAmount,
);
},
);
}
return Container(color: Colors.transparent, height: 1);
},
)
: Center(
child: Text(
key: ValueKey('transactions_page_placeholder_transactions_text_key'),
S.of(context).placeholder_transactions,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
);
},
),
),
],
),
),
);
}
}
@@ -1,66 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ActionButton extends StatelessWidget {
ActionButton({
required this.image,
required this.title,
this.route,
this.onClick,
this.alignment = Alignment.center,
this.textColor,
super.key,
});
final Image image;
final String title;
final String? route;
final Alignment alignment;
final VoidCallback? onClick;
final Color? textColor;
@override
Widget build(BuildContext context) {
return TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
if (route?.isNotEmpty ?? false) {
Navigator.of(context, rootNavigator: true).pushNamed(route!);
} else {
onClick?.call();
}
},
child: SizedBox.expand(
child: Container(
color: Colors.transparent,
alignment: alignment,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
const SizedBox(height: 4),
Flexible(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.fade,
softWrap: false,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: textColor ?? Theme.of(context).colorScheme.onSurface,
),
),
),
],
),
),
),
);
}
}
@@ -1,68 +0,0 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
class HomeScreenAccountWidget extends StatelessWidget {
HomeScreenAccountWidget({this.walletName, this.accountName});
final String? walletName;
final String? accountName;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
await showPopUp<void>(context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
},
behavior: HitTestBehavior.opaque,
child: Padding(
padding: EdgeInsets.all(25.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
walletName ?? '',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
SizedBox(
height: 5.0,
),
Row(
children: [
Padding(padding: EdgeInsets.only(left: 20)),
Container(
child: Text(
accountName ?? '',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
),
Container(
child: Icon(
Icons.keyboard_arrow_down,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
],
),
],
),
),
);
}
}
@@ -1,97 +0,0 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:flutter/material.dart';
class InfoCard extends StatelessWidget {
final String leftButtonTitle;
final String rightButtonTitle;
final String title;
final String description;
final String image;
final Function() leftButtonAction;
final Function() rightButtonAction;
final Widget? hintWidget;
const InfoCard({
super.key,
required this.title,
required this.description,
required this.leftButtonTitle,
required this.rightButtonTitle,
required this.leftButtonAction,
required this.rightButtonAction,
required this.image,
this.hintWidget,
});
@override
Widget build(BuildContext context) {
return DashBoardRoundedCardWidget(
marginH: 0,
marginV: 0,
customBorder: 30,
title: title,
subTitle: description,
hint: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (hintWidget != null) hintWidget!,
if (hintWidget != null) SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: leftButtonAction,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Text(
leftButtonTitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: rightButtonAction,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Text(
rightButtonTitle,
maxLines: 1,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
),
],
),
],
),
onTap: () => {},
icon: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
shape: BoxShape.circle,
),
child: CakeImageWidget(
imageUrl: image,
height: 40,
width: 40,
),
),
);
}
}
@@ -1,302 +0,0 @@
import 'dart:math' as math;
import 'dart:ui';
import 'package:cake_wallet/entities/page_indicator_actions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
class PageIndicator extends StatefulWidget {
const PageIndicator({
super.key,
required this.controller,
required this.dashboardViewModel,
});
final PageController controller;
final DashboardViewModel dashboardViewModel;
@override
State<PageIndicator> createState() => _PageIndicatorState();
}
class _PageIndicatorState extends State<PageIndicator> {
static const barHeight = 37.0;
static const iconWidth = 16.0;
static const iconHeight = 16.0;
static const iconSpacing = 5.0;
static const barHorizontalPadding = 4.0;
static const pillHorizontalPadding = 8.0;
static const edgePadding = 5.0;
static const barBorderRadius = 50.0;
static const pillBorderRadius = 50.0;
static const pageSwitchDuration = Duration(milliseconds: 300);
static const pillMoveDuration = Duration(milliseconds: 300);
static const pillResizeDuration = Duration(milliseconds: 200);
static const textStyle = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
);
late int selectedIndex;
late final VoidCallback _pageListener;
@override
void initState() {
super.initState();
selectedIndex = widget.controller.initialPage;
_pageListener = () {
final page = widget.controller.page;
if (page == null) return;
final rounded = page.round();
if (rounded != selectedIndex && mounted) {
setState(() {
selectedIndex = rounded;
});
}
};
widget.controller.addListener(_pageListener);
}
@override
void dispose() {
widget.controller.removeListener(_pageListener);
super.dispose();
}
void _onItemTap(int index) {
if (index == selectedIndex) return;
widget.controller.animateToPage(
index,
duration: pageSwitchDuration,
curve: Curves.easeOutCubic,
);
}
double _estimateItemWidthForAction(BuildContext context, PageIndicatorActions action,
{Color? color}) {
final text = action.name(context);
final textPainter = TextPainter(
text: TextSpan(text: text, style: textStyle.copyWith(color: color)),
maxLines: 1,
textDirection: Directionality.of(context),
textScaler: MediaQuery.of(context).textScaler,
)..layout();
return iconWidth + iconSpacing + textPainter.width + pillHorizontalPadding * 2;
}
@override
Widget build(BuildContext context) {
final visibleActions = PageIndicatorActions.all.where((action) {
return action.isEnabled?.call(widget.dashboardViewModel) ?? true;
}).toList();
// Don't show indicator if less than 2 actions
if (visibleActions.length < 2) return const SizedBox.shrink();
selectedIndex = selectedIndex.clamp(0, visibleActions.length - 1);
// Theme setup
final theme = Theme.of(context);
final backgroundColor = theme.colorScheme.surfaceContainer.withAlpha(122);
final pillColor = theme.colorScheme.onSurface.withAlpha(30);
final activeColor = theme.colorScheme.onSurface;
final inactiveColor = theme.colorScheme.primary;
// Size calculations
final screenWidth = MediaQuery.of(context).size.width;
final isRTL = Directionality.of(context) == TextDirection.rtl;
final pillWidth = _estimateItemWidthForAction(
context,
visibleActions[selectedIndex],
color: activeColor,
);
final actionWidths = [
for (final action in visibleActions)
_estimateItemWidthForAction(context, action, color: inactiveColor)
];
final totalItemsWidth =
actionWidths.fold(0.0, (sum, width) => sum + width) + barHorizontalPadding * 2;
final barWidth = math.min(totalItemsWidth, screenWidth * 0.95);
final count = visibleActions.length;
final firstLeft = edgePadding;
final secondLeft = barWidth - (isRTL ? actionWidths.first : actionWidths[1]) - edgePadding;
final List<double> positions;
if (count == 2) {
positions = [firstLeft, secondLeft];
} else {
final lastLeft = barWidth - (isRTL ? actionWidths.first : actionWidths.last) - edgePadding;
final centerLeft =
(isRTL ? actionWidths.last + edgePadding : actionWidths.first + edgePadding);
positions = [firstLeft, centerLeft, lastLeft];
}
final left = (isRTL ? positions.reversed.toList() : positions)[selectedIndex];
final currentAction = visibleActions[selectedIndex];
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(top: 10),
child: Container(
width: barWidth,
child: ClipRRect(
borderRadius: BorderRadius.circular(barBorderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
child: Container(
height: barHeight,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(barBorderRadius),
),
child: Stack(
alignment: Alignment.center,
children: [
AnimatedPill(
left: left,
pillColor: pillColor,
currentAction: currentAction,
pillIconHeight: iconHeight,
pillIconWidth: iconWidth,
pillIconSpacing: iconSpacing,
pillBorderRadius: pillBorderRadius,
contentColor: activeColor,
estimateWidthForAction: pillWidth,
pillTextStyle: textStyle,
pillMoveDuration: pillMoveDuration,
pillResizeDuration: pillResizeDuration,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: barHorizontalPadding),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
for (int i = 0; i < visibleActions.length; i++)
Semantics(
button: true,
label: visibleActions[i].name(context),
value: i == selectedIndex ? 'Selected' : null,
hint: 'Double tap to open ${visibleActions[i].name(context)} page',
onTap: () => _onItemTap(i),
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => _onItemTap(i),
child: Container(
height: barHeight,
width: i == selectedIndex
? pillWidth
: _estimateItemWidthForAction(context, visibleActions[i]),
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
TweenAnimationBuilder<Color?>(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
tween: ColorTween(
begin: inactiveColor,
end: i == selectedIndex ? activeColor : inactiveColor,
),
builder: (context, color, _) => SvgPicture.asset(
visibleActions[i].image,
width: iconWidth,
height: iconHeight,
colorFilter: ColorFilter.mode(color!, BlendMode.srcIn),
),
),
SizedBox(width: iconSpacing),
TweenAnimationBuilder<Color?>(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
tween: ColorTween(
begin: inactiveColor,
end: i == selectedIndex ? activeColor : inactiveColor,
),
builder: (context, color, _) => Text(
visibleActions[i].name(context),
style: textStyle.copyWith(color: color),
overflow: TextOverflow.fade,
softWrap: false,
),
),
],
),
),
),
),
],
),
)
],
)),
),
),
),
),
);
}
}
class AnimatedPill extends StatelessWidget {
const AnimatedPill({
super.key,
required this.left,
required this.pillColor,
required this.contentColor,
required this.pillIconHeight,
required this.pillIconWidth,
required this.pillBorderRadius,
required this.currentAction,
required this.estimateWidthForAction,
required this.pillIconSpacing,
required this.pillTextStyle,
required this.pillMoveDuration,
required this.pillResizeDuration,
});
final double left;
final Color pillColor;
final Color contentColor;
final double pillIconHeight;
final double pillIconWidth;
final double pillBorderRadius;
final PageIndicatorActions currentAction;
final double estimateWidthForAction;
final double pillIconSpacing;
final TextStyle pillTextStyle;
final Duration pillMoveDuration;
final Duration pillResizeDuration;
@override
Widget build(BuildContext context) {
return AnimatedPositioned(
duration: pillMoveDuration,
curve: Curves.easeOutCubic,
left: left,
top: 4,
bottom: 4,
child: AnimatedContainer(
duration: pillResizeDuration,
curve: Curves.easeOutCubic,
width: estimateWidthForAction,
decoration:
BoxDecoration(color: pillColor, borderRadius: BorderRadius.circular(pillBorderRadius)),
clipBehavior: Clip.hardEdge,
alignment: Alignment.center,
),
);
}
}
@@ -1,163 +0,0 @@
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/src/widgets/rounded_checkbox.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
class PresentReceiveOptionPicker extends StatelessWidget {
PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color});
final ReceiveOptionViewModel receiveOptionViewModel;
final Color color;
@override
Widget build(BuildContext context) {
final arrowBottom = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).colorScheme.primary,
height: 6,
);
return Padding(
padding: EdgeInsets.only(left: 6),
child: TextButton(
onPressed: () => _showPicker(context),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
splashFactory: NoSplash.splashFactory,
foregroundColor: WidgetStateProperty.all(Colors.transparent),
overlayColor: WidgetStateProperty.all(Colors.transparent),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
S.current.receive,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 17.0,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
Row(
children: [
Observer(
builder: (_) => Text(
receiveOptionViewModel.selectedReceiveOption
.toString()
.replaceAll(RegExp(r'silent payments', caseSensitive: false),
S.current.silent_payments)
.replaceAll(RegExp(r'default', caseSensitive: false),
S.current.string_default),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
],
),
SizedBox(width: 5),
Padding(
padding: EdgeInsets.only(top: 12),
child: arrowBottom,
)
],
),
));
}
void _showPicker(BuildContext context) async {
await showPopUp<void>(
builder: (BuildContext popUpContext) => Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: Stack(
alignment: AlignmentDirectional.center,
children: [
AlertBackground(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Container(
margin: EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Theme.of(context).colorScheme.surface,
),
child: Padding(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: (ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: receiveOptionViewModel.options.length,
itemBuilder: (_, index) {
final option = receiveOptionViewModel.options[index];
return InkWell(
onTap: () {
Navigator.pop(popUpContext);
receiveOptionViewModel.selectReceiveOption(option);
},
child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Observer(builder: (_) {
final value = receiveOptionViewModel.selectedReceiveOption;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
option
.toString()
.replaceAll(
RegExp(r'silent payments', caseSensitive: false),
S.current.silent_payments)
.replaceAll(RegExp(r'default', caseSensitive: false),
S.current.string_default),
textAlign: TextAlign.left,
style: textSmall(
color: Theme.of(context).colorScheme.onSurface,
).copyWith(
fontWeight:
value == option ? FontWeight.w800 : FontWeight.w500,
)),
RoundedCheckbox(
value: value == option,
)
],
);
}),
),
);
},
separatorBuilder: (_, index) => SizedBox(height: 30),
)),
),
),
Spacer()
],
),
),
AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40)
],
),
),
context: context,
);
}
}
@@ -1,87 +0,0 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/core/sync_status_title.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
class SyncIndicator extends StatelessWidget {
SyncIndicator({
required this.dashboardViewModel,
required this.onTap,
super.key,
});
final DashboardViewModel dashboardViewModel;
final Function() onTap;
@override
Widget build(BuildContext context) {
return Observer(builder: (_) {
final syncIndicatorWidth = 237.0;
final status = dashboardViewModel.status;
final statusText =
syncStatusTitle(status, dashboardViewModel.settingsStore.syncStatusDisplayMode);
final progress = status.progress();
final indicatorOffset = progress * syncIndicatorWidth;
final indicatorWidth = progress < 1
? indicatorOffset > 0
? indicatorOffset
: 0.0
: syncIndicatorWidth;
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: GestureDetector(
onTap: onTap,
onLongPress: dashboardViewModel.toggleSwitchStatusDisplayMode,
child: Container(
height: 30,
width: syncIndicatorWidth,
color: Theme.of(context).colorScheme.surfaceContainerLow,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
progress <= 1
? Positioned(
left: 0,
top: 0,
bottom: 0,
child: Container(
width: indicatorWidth,
height: 30,
color: Theme.of(context).colorScheme.surfaceContainer,
))
: Offstage(),
Padding(
padding: EdgeInsets.only(left: 24, right: 24),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SyncIndicatorIcon(
isSynced: status is SyncedSyncStatus,
showTorIcon: dashboardViewModel.builtinTor,
size: dashboardViewModel.builtinTor ? 16 : 6,
),
Padding(
padding: EdgeInsets.only(left: 6),
child: Text(
statusText,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
)
],
),
)
],
),
),
));
});
}
}
-803
View File
@@ -1,803 +0,0 @@
import 'package:cake_wallet/core/address_resolver/address_resolver_service.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
import 'package:cake_wallet/src/widgets/add_template_button.dart';
import 'package:cake_wallet/utils/debounce.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
class ExchangePage extends BasePage {
ExchangePage(
this.exchangeViewModel, this.authService, this.adrResService, this.initialPaymentRequest) {
depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
receiveWalletName = exchangeViewModel.receiveCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
}
final ExchangeViewModel exchangeViewModel;
final AuthService authService;
final AddressResolverService adrResService;
final PaymentRequest? initialPaymentRequest;
final depositKey = GlobalKey<ExchangeCardState>();
final receiveKey = GlobalKey<ExchangeCardState>();
final _formKey = GlobalKey<FormState>();
final _depositAmountFocus = FocusNode();
final _depositAddressFocus = FocusNode();
final _receiveAmountFocus = FocusNode();
final _receiveAddressFocus = FocusNode();
final _receiveAmountDebounce = Debounce(Duration(milliseconds: 500));
Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
var _isReactionsSet = false;
late final String? depositWalletName;
late final String? receiveWalletName;
@override
String get title => S.current.exchange;
@override
bool get gradientBackground => true;
@override
bool get gradientAll => true;
@override
bool get resizeToAvoidBottomInset => false;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
bool get _shouldWaitTillSynced =>
[CryptoCurrency.xmr, CryptoCurrency.btc, CryptoCurrency.ltc]
.contains(exchangeViewModel.depositCurrency) &&
!(exchangeViewModel.status is SyncedSyncStatus);
@override
Widget middle(BuildContext context) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(right: 6.0),
child: Observer(
builder: (_) =>
SyncIndicatorIcon(isSynced: exchangeViewModel.status is SyncedSyncStatus),
),
),
PresentProviderPicker(exchangeViewModel: exchangeViewModel)
],
);
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).reset,
onPressed: () {
_formKey.currentState?.reset();
exchangeViewModel.reset();
},
);
@override
Widget? leading(BuildContext context) {
final _backButton = Icon(
Icons.arrow_back_ios,
color: Theme.of(context).colorScheme.primary,
size: 16,
);
final _closeButton = currentTheme.isDark ? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
return MergeSemantics(
child: SizedBox(
height: isMobileView ? 37 : 45,
width: isMobileView ? 37 : 45,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
child: TextButton(
style: ButtonStyle(
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent),
),
onPressed: () => onClose(context),
child: !isMobileView ? _closeButton : _backButton,
),
),
),
),
);
}
@override
Widget body(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).colorScheme.surfaceContainerHighest,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _depositAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]),
KeyboardActionsItem(
focusNode: _receiveAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()])
],
),
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Observer(
builder: (_) => Column(
children: <Widget>[
_exchangeCardsSection(context),
Padding(
padding: EdgeInsets.only(top: 12, left: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
StandardCheckbox(
value: exchangeViewModel.isFixedRateMode,
caption: S.of(context).fixed_rate,
onChanged: (value) {
if (value) {
exchangeViewModel.enableFixedRateMode();
} else {
exchangeViewModel.isFixedRateMode = false;
}
},
),
],
),
),
SizedBox(height: 30),
_buildTemplateSection(context)
],
),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Observer(
builder: (_) {
final description = exchangeViewModel.isFixedRateMode
? exchangeViewModel.isAvailableInSelected
? S.of(context).amount_is_guaranteed
: S.of(context).fixed_pair_not_supported
: exchangeViewModel.isAvailableInSelected
? S.of(context).amount_is_estimate
: S.of(context).this_pair_is_not_supported_warning;
return Row(
children: [
if (description == S.of(context).this_pair_is_not_supported_warning)
Expanded(
child: Container(
alignment: Alignment.centerRight,
child: Icon(Icons.warning_amber_rounded,
color: Theme.of(context).colorScheme.error, size: 26),
),
),
Expanded(
flex: 8,
child: Text(
description,
textAlign: TextAlign.center,
softWrap: true,
overflow: TextOverflow.ellipsis,
maxLines: 3,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
),
],
);
},
),
),
Observer(
builder: (_) => LoadingPrimaryButton(
key: ValueKey('exchange_page_exchange_button_key'),
text: exchangeViewModel.isAvailableInSelected
? S.of(context).swap
: S.of(context).change_selected_exchanges,
onPressed: exchangeViewModel.isAvailableInSelected
? () {
FocusScope.of(context).unfocus();
if (_formKey.currentState != null &&
_formKey.currentState!.validate()) {
if (_shouldWaitTillSynced) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).exchange,
alertContent: S.of(context).exchange_sync_alert_content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
} else {
final check = exchangeViewModel.shouldDisplayTOTP();
authService.authenticateAction(
context,
conditionToDetermineIfToUse2FA: check,
onAuthSuccess: (value) {
if (value) {
exchangeViewModel.createTrade();
}
},
);
}
}
}
: () => PresentProviderPicker(exchangeViewModel: exchangeViewModel)
.presentProviderPicker(context),
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: exchangeViewModel.selectedProviders.isEmpty,
isLoading: exchangeViewModel.tradeState is TradeIsCreating,
),
),
],
),
),
),
),
);
}
Widget _buildTemplateSection(BuildContext context) {
return Container(
height: 40,
width: double.infinity,
padding: EdgeInsets.only(left: 24),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Observer(
builder: (_) {
final templates = exchangeViewModel.templates;
return Row(
children: <Widget>[
AddTemplateButton(
onTap: () => Navigator.of(context).pushNamed(Routes.exchangeTemplate),
currentTemplatesLength: templates.length,
),
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: templates.length,
itemBuilder: (context, index) {
final template = templates[index];
return TemplateTile(
key: UniqueKey(),
amount: template.amount,
from: template.depositCurrencyTitle,
to: template.receiveCurrencyTitle,
onTap: () {
applyTemplate(context, exchangeViewModel, template);
},
onRemove: () {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).template,
alertContent: S.of(context).confirm_delete_template,
rightButtonText: S.of(context).delete,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
Navigator.of(dialogContext).pop();
exchangeViewModel.removeTemplate(template: template);
exchangeViewModel.updateTemplate();
},
actionLeftButton: () => Navigator.of(dialogContext).pop(),
);
},
);
},
);
},
),
],
);
},
),
),
);
}
void applyTemplate(
BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
final depositCryptoCurrency =
CryptoCurrency.safeParseCurrencyFromString(template.depositCurrency);
final receiveCryptoCurrency =
CryptoCurrency.safeParseCurrencyFromString(template.receiveCurrency);
if (depositCryptoCurrency == null || receiveCryptoCurrency == null) {
///TO DO: add support for user tokens
return;
}
exchangeViewModel.changeDepositCurrency(currency: depositCryptoCurrency);
exchangeViewModel.changeReceiveCurrency(currency: receiveCryptoCurrency);
exchangeViewModel.changeDepositAmount(amount: template.amount);
exchangeViewModel.depositAddress = template.depositAddress;
exchangeViewModel.receiveAddress = template.receiveAddress;
exchangeViewModel.isReceiveAmountEntered = false;
exchangeViewModel.isFixedRateMode = false;
var domain = template.depositAddress;
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, depositCryptoCurrency);
domain = template.receiveAddress;
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, receiveCryptoCurrency);
}
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
if (_isReactionsSet) {
return;
}
if (exchangeViewModel.feesViewModel.isLowFee) {
_showFeeAlert(context);
}
final depositAddressController = depositKey.currentState!.addressController;
final depositAmountController = depositKey.currentState!.amountController;
final receiveAddressController = receiveKey.currentState!.addressController;
final receiveAmountController = receiveKey.currentState!.amountController;
final limitsState = exchangeViewModel.limitsState;
if (limitsState is LimitsLoadedSuccessfully) {
final min = limitsState.limits.min != null ? limitsState.limits.min.toString() : null;
final max = limitsState.limits.max != null ? limitsState.limits.max.toString() : null;
final key = exchangeViewModel.isFixedRateMode ? receiveKey : depositKey;
key.currentState!.changeLimits(min: min, max: max);
}
_onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
_onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
reaction(
(_) => exchangeViewModel.wallet.name,
(_) =>
_onWalletNameChange(exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
reaction(
(_) => exchangeViewModel.wallet.name,
(_) =>
_onWalletNameChange(exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
reaction((_) => exchangeViewModel.receiveCurrency,
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, receiveKey));
reaction((_) => exchangeViewModel.depositCurrency,
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, depositKey));
reaction((_) => exchangeViewModel.depositAmount, (String amount) {
if (exchangeViewModel.isSendAllEnabled) {
depositAmountController.text = S.of(context).all;
} else if (depositAmountController.text != amount && amount != S.of(context).all) {
depositAmountController.text = amount;
}
});
reaction((_) => exchangeViewModel.depositAddress, (String address) {
if (depositKey.currentState!.addressController.text != address) {
depositKey.currentState!.addressController.text = address;
}
});
reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {
depositKey.currentState!.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => exchangeViewModel.receiveAmount, (String amount) {
if (receiveKey.currentState!.amountController.text != amount) {
receiveKey.currentState!.amountController.text = amount;
}
});
reaction((_) => exchangeViewModel.receiveAddress, (String address) {
if (receiveKey.currentState!.addressController.text != address) {
receiveKey.currentState!.addressController.text = address;
}
});
reaction((_) => exchangeViewModel.isReceiveAmountEditable, (bool isReceiveAmountEditable) {
receiveKey.currentState!.isAmountEditable(isEditable: isReceiveAmountEditable);
});
reaction((_) => exchangeViewModel.tradeState, (ExchangeTradeState state) {
if (state is TradeIsCreatedFailure) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
key: ValueKey('exchange_page_trade_creation_failure_dialog_key'),
buttonKey: ValueKey('exchange_page_trade_creation_failure_dialog_button_key'),
alertTitle: S.of(context).provider_error(state.title),
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
if (state is TradeIsCreatedSuccessfully) {
exchangeViewModel.reset();
(exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.thorChain ||
exchangeViewModel.tradesStore.trade?.provider ==
ExchangeProviderDescription.chainflip)
? Navigator.of(context).pushReplacementNamed(Routes.exchangeTrade)
: Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm);
}
});
reaction((_) => exchangeViewModel.limitsState, (LimitsState state) {
String? min;
String? max;
if (state is LimitsLoadedSuccessfully) {
min = state.limits.min != null ? state.limits.min.toString() : null;
max = state.limits.max != null ? state.limits.max.toString() : null;
}
if (state is LimitsLoadedFailure) {
min = '0';
max = '0';
}
if (state is LimitsIsLoading) {
min = '...';
max = '...';
}
if (exchangeViewModel.isFixedRateMode) {
depositKey.currentState!.changeLimits(min: null, max: null);
receiveKey.currentState!.changeLimits(min: min, max: max);
} else {
depositKey.currentState!.changeLimits(min: min, max: max);
receiveKey.currentState!.changeLimits(min: null, max: null);
}
});
reaction((_) => exchangeViewModel.bestRate, (double rate) {
if (exchangeViewModel.isFixedRateMode) {
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
} else {
if (depositAmountController.text == S.current.all)
exchangeViewModel.changeDepositAmount(amount: exchangeViewModel.depositAmount);
else
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
}
});
depositAddressController
.addListener(() => exchangeViewModel.depositAddress = depositAddressController.text);
depositAmountController.addListener(() {
if (depositAmountController.text != exchangeViewModel.depositAmount &&
depositAmountController.text != S.of(context).all) {
exchangeViewModel.isSendAllEnabled = false;
final isThorChain = exchangeViewModel.selectedProviders
.any((provider) => provider is ThorChainExchangeProvider);
final isChainflip = exchangeViewModel.selectedProviders
.any((provider) => provider is ChainflipExchangeProvider);
_depositAmountDebounce = isThorChain || isChainflip
? Debounce(Duration(milliseconds: 1000))
: Debounce(Duration(milliseconds: 500));
_depositAmountDebounce.run(() {
exchangeViewModel.calculateBestRate();
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
exchangeViewModel.isReceiveAmountEntered = false;
});
}
});
receiveAddressController
.addListener(() => exchangeViewModel.receiveAddress = receiveAddressController.text);
receiveAmountController.addListener(() {
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
_receiveAmountDebounce.run(() {
exchangeViewModel.calculateBestRate();
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
exchangeViewModel.isReceiveAmountEntered = true;
});
}
});
reaction((_) => exchangeViewModel.wallet.walletAddresses.addressForExchange, (String address) {
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
depositKey.currentState!.changeAddress(address: address);
}
if (exchangeViewModel.receiveCurrency == CryptoCurrency.xmr) {
receiveKey.currentState!.changeAddress(address: address);
}
});
_depositAddressFocus.addListener(() async {
if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) {
final domain = depositAddressController.text;
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
}
});
_receiveAddressFocus.addListener(() async {
if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) {
final domain = receiveAddressController.text;
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
}
});
_receiveAmountFocus.addListener(() {
if (_receiveAmountFocus.hasFocus) {
exchangeViewModel.enableFixedRateMode();
}
// exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
});
_depositAmountFocus.addListener(() {
exchangeViewModel.isFixedRateMode = false;
// exchangeViewModel.changeDepositAmount(
// amount: depositAmountController.text);
});
if (initialPaymentRequest != null) {
try {
exchangeViewModel.receiveCurrency =
CryptoCurrency.fromString(initialPaymentRequest!.scheme);
exchangeViewModel.setCanonicalReceiveAmount(initialPaymentRequest!.amount);
exchangeViewModel.receiveAddress = initialPaymentRequest!.address;
} catch (e) {
printV('error: ${e.toString()}');
// TODO
}
}
_isReactionsSet = true;
}
void _onCurrencyChange(CryptoCurrency currency, ExchangeViewModel exchangeViewModel,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = exchangeViewModel.useSameWalletAddress(currency);
key.currentState!.changeSelectedCurrency(currency);
key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
key.currentState!.changeAddress(
address:
isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.addressForExchange : '');
}
void _onWalletNameChange(ExchangeViewModel exchangeViewModel, CryptoCurrency currency,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
if (isCurrentTypeWallet) {
key.currentState!.changeWalletName(exchangeViewModel.wallet.name);
key.currentState!.addressController.text =
exchangeViewModel.wallet.walletAddresses.addressForExchange;
} else if (key.currentState!.addressController.text ==
exchangeViewModel.wallet.walletAddresses.addressForExchange) {
key.currentState!.changeWalletName('');
key.currentState!.addressController.text = '';
}
}
Future<String> fetchParsedAddress(
BuildContext context, String domain, CryptoCurrency currency) async {
final parsedAddresses = await adrResService.resolve(
query: domain, wallet: exchangeViewModel.wallet, currency: currency);
return parsedAddresses.isNotEmpty
? (parsedAddresses.first.parsedAddressByCurrencyMap[currency] ?? domain)
: domain;
}
void _showFeeAlert(BuildContext context) async {
await Future<void>.delayed(Duration(seconds: 1));
final confirmed = await showPopUp<bool>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).low_fee,
alertContent: S.of(context).low_fee_alert,
leftButtonText: S.of(context).ignor,
rightButtonText: S.of(context).use_suggested,
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
actionRightButton: () => Navigator.of(dialogContext).pop(true));
}) ??
false;
if (confirmed) {
exchangeViewModel.feesViewModel.setDefaultTransactionPriority();
}
}
void disposeBestRateSync() => exchangeViewModel.bestRateSync.cancel();
Widget _exchangeCardsSection(BuildContext context) {
final firstExchangeCard = Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'deposit_exchange_card',
onDispose: disposeBestRateSync,
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount:
exchangeViewModel.hasAllAmount ? () => exchangeViewModel.enableSendAllAmount() : null,
isAllAmountEnabled: exchangeViewModel.isSendAllEnabled,
amountFocusNode: _depositAmountFocus,
addressFocusNode: _depositAddressFocus,
key: depositKey,
title: S.of(context).you_will_send,
initialCurrency: exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName ?? '',
initialAddress: exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.depositAddress,
initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
isAmountEstimated: false,
hasRefundAddress: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: exchangeViewModel.depositCurrencies,
onCurrencySelected: (currency) {
exchangeViewModel.changeDepositCurrency(currency: currency);
},
currencyButtonColor: Colors.transparent,
addressButtonsColor: Colors.transparent,
borderColor: Theme.of(context).colorScheme.outlineVariant,
fillColor: Theme.of(context).colorScheme.surfaceContainer,
currencyValueValidator: (value) {
return !exchangeViewModel.isFixedRateMode && value != S.of(context).all
? AmountValidator(
isAutovalidate: true,
currency: exchangeViewModel.depositCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
minValue: exchangeViewModel.limits.min.toString(),
maxValue: exchangeViewModel.limits.max.toString(),
).call(value)
: null;
},
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency),
onPushPasteButton: (context) async {
final domain = exchangeViewModel.depositAddress;
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
},
onPushAddressBookButton: (context) async {
final domain = exchangeViewModel.depositAddress;
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
},
useSatoshis: exchangeViewModel.useDepositBaseUnit,
),
);
final secondExchangeCard = Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'receive_exchange_card',
onDispose: disposeBestRateSync,
amountFocusNode: _receiveAmountFocus,
addressFocusNode: _receiveAddressFocus,
key: receiveKey,
title: S.of(context).you_will_get,
initialCurrency: exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName ?? '',
initialAddress: exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.receiveAddress,
initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable,
isAmountEstimated: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: exchangeViewModel.receiveCurrencies,
onCurrencySelected: (currency) =>
exchangeViewModel.changeReceiveCurrency(currency: currency),
currencyButtonColor: Colors.transparent,
addressButtonsColor: Colors.transparent,
borderColor: Theme.of(context).colorScheme.outlineVariant,
fillColor: Theme.of(context).colorScheme.surfaceContainerLow,
currencyValueValidator: (value) {
return exchangeViewModel.isFixedRateMode
? AmountValidator(
isAutovalidate: true,
currency: exchangeViewModel.receiveCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
minValue: exchangeViewModel.limits.min.toString(),
maxValue: exchangeViewModel.limits.max.toString(),
).call(value)
: null;
},
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency),
onPushPasteButton: (context) async {
final domain = exchangeViewModel.receiveAddress;
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
},
onPushAddressBookButton: (context) async {
final domain = exchangeViewModel.receiveAddress;
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
},
useSatoshis: exchangeViewModel.useReceiveBaseUnit,
),
);
return responsiveLayoutUtil.shouldRenderMobileUI
? MobileExchangeCardsSection(
firstExchangeCard: firstExchangeCard,
secondExchangeCard: secondExchangeCard,
)
: DesktopExchangeCardsSection(
firstExchangeCard: firstExchangeCard,
secondExchangeCard: secondExchangeCard,
);
}
}
@@ -1,378 +0,0 @@
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
class ExchangeTemplatePage extends BasePage {
ExchangeTemplatePage(this.exchangeViewModel);
final ExchangeViewModel exchangeViewModel;
final depositKey = GlobalKey<ExchangeCardState>();
final receiveKey = GlobalKey<ExchangeCardState>();
final _formKey = GlobalKey<FormState>();
final _depositAmountFocus = FocusNode();
final _receiveAmountFocus = FocusNode();
var _isReactionsSet = false;
@override
bool get gradientAll => true;
@override
String get title => S.current.exchange_new_template;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
Widget trailing(BuildContext context) =>
PresentProviderPicker(exchangeViewModel: exchangeViewModel);
@override
Widget body(BuildContext context) {
final arrowBottomPurple = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).colorScheme.primary,
height: 8,
);
final arrowBottomCakeGreen = Image.asset(
'assets/images/arrow_bottom_cake_green.png',
color: Theme.of(context).colorScheme.primary,
height: 8,
);
final depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
final receiveWalletName = exchangeViewModel.receiveCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).colorScheme.surfaceVariant,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _depositAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]),
KeyboardActionsItem(
focusNode: _receiveAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()])
],
),
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
padding: EdgeInsets.only(bottom: 32),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
color: Theme.of(context).colorScheme.surfaceContainerLow,
),
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'deposit_exchange_template_card',
amountFocusNode: _depositAmountFocus,
key: depositKey,
title: S.of(context).you_will_send,
initialCurrency: exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName ?? '',
initialAddress: exchangeViewModel.depositCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.depositAddress,
initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
isAmountEstimated: false,
hasRefundAddress: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeViewModel.changeDepositCurrency(currency: currency),
imageArrow: arrowBottomPurple,
currencyButtonColor: Colors.transparent,
addressButtonsColor:
Theme.of(context).colorScheme.surfaceContainerHighest,
borderColor: Theme.of(context).colorScheme.outlineVariant,
fillColor: Theme.of(context).colorScheme.surfaceContainer,
currencyValueValidator: AmountValidator(
currency: exchangeViewModel.depositCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
),
),
),
),
Padding(
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'receive_exchange_template_card',
amountFocusNode: _receiveAmountFocus,
key: receiveKey,
title: S.of(context).you_will_get,
initialCurrency: exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName ?? '',
initialAddress: exchangeViewModel.receiveCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.receiveAddress,
initialIsAmountEditable: false,
isAmountEstimated: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: exchangeViewModel.receiveCurrencies,
onCurrencySelected: (currency) =>
exchangeViewModel.changeReceiveCurrency(currency: currency),
imageArrow: arrowBottomCakeGreen,
currencyButtonColor: Colors.transparent,
addressButtonsColor:
Theme.of(context).colorScheme.surfaceContainerHighest,
borderColor: Theme.of(context).colorScheme.outlineVariant,
fillColor: Theme.of(context).colorScheme.surfaceContainerLow,
currencyValueValidator: AmountValidator(
currency: exchangeViewModel.receiveCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
),
),
),
)
],
),
),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Observer(
builder: (_) => Center(
child: Text(
S.of(context).amount_is_estimate,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
),
),
),
PrimaryButton(
onPressed: () {
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
exchangeViewModel.addTemplate(
amount: exchangeViewModel.depositAmount,
depositCurrency: exchangeViewModel.depositCurrency.name,
depositCurrencyTitle: exchangeViewModel.depositCurrency.title +
' ${exchangeViewModel.depositCurrency.tag ?? ''}',
receiveCurrency: exchangeViewModel.receiveCurrency.name,
receiveCurrencyTitle: exchangeViewModel.receiveCurrency.title +
' ${exchangeViewModel.receiveCurrency.tag ?? ''}',
provider: exchangeViewModel.provider.toString(),
depositAddress: exchangeViewModel.depositAddress,
receiveAddress: exchangeViewModel.receiveAddress);
exchangeViewModel.updateTemplate();
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary),
]),
))));
}
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
if (_isReactionsSet) {
return;
}
final depositAddressController = depositKey.currentState!.addressController;
final depositAmountController = depositKey.currentState!.amountController;
final receiveAddressController = receiveKey.currentState!.addressController;
final receiveAmountController = receiveKey.currentState!.amountController;
final limitsState = exchangeViewModel.limitsState;
// FIXME: FIXME
// final limitsState = exchangeViewModel.limitsState;
//
// if (limitsState is LimitsLoadedSuccessfully) {
// final min = limitsState.limits.min != null
// ? limitsState.limits.min.toString()
// : null;
// final max = limitsState.limits.max != null
// ? limitsState.limits.max.toString()
// : null;
// final key = depositKey;
// key.currentState.changeLimits(min: min, max: max);
// }
_onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
_onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
reaction(
(_) => exchangeViewModel.wallet.name,
(String _) =>
_onWalletNameChange(exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
reaction(
(_) => exchangeViewModel.wallet.name,
(String _) =>
_onWalletNameChange(exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
reaction((_) => exchangeViewModel.receiveCurrency,
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, receiveKey));
reaction((_) => exchangeViewModel.depositCurrency,
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, depositKey));
reaction((_) => exchangeViewModel.depositAmount, (String amount) {
if (depositKey.currentState!.amountController.text != amount && amount != S.of(context).all) {
depositKey.currentState!.amountController.text = amount;
}
});
reaction((_) => exchangeViewModel.depositAddress, (String address) {
if (depositKey.currentState!.addressController.text != address) {
depositKey.currentState!.addressController.text = address;
}
});
reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {
depositKey.currentState!.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => exchangeViewModel.receiveAmount, (String amount) {
if (receiveKey.currentState!.amountController.text != amount) {
receiveKey.currentState!.amountController.text = amount;
}
});
reaction((_) => exchangeViewModel.receiveAddress, (String address) {
if (receiveKey.currentState!.addressController.text != address) {
receiveKey.currentState!.addressController.text = address;
}
});
reaction((_) => exchangeViewModel.provider, (ExchangeProvider? provider) {
receiveKey.currentState!.isAmountEditable(isEditable: false);
});
/*reaction((_) => exchangeViewModel.limitsState, (LimitsState state) {
String min;
String max;
if (state is LimitsLoadedSuccessfully) {
min = state.limits.min != null ? state.limits.min.toString() : null;
max = state.limits.max != null ? state.limits.max.toString() : null;
}
if (state is LimitsLoadedFailure) {
min = '0';
max = '0';
}
if (state is LimitsIsLoading) {
min = '...';
max = '...';
}
depositKey.currentState.changeLimits(min: min, max: max);
receiveKey.currentState.changeLimits(min: null, max: null);
});*/
depositAddressController
.addListener(() => exchangeViewModel.depositAddress = depositAddressController.text);
depositAmountController.addListener(() {
if (depositAmountController.text != exchangeViewModel.depositAmount &&
exchangeViewModel.depositAmount != S.of(context).all) {
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
exchangeViewModel.isReceiveAmountEntered = false;
}
});
receiveAddressController
.addListener(() => exchangeViewModel.receiveAddress = receiveAddressController.text);
receiveAmountController.addListener(() {
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
exchangeViewModel.isReceiveAmountEntered = true;
}
});
reaction((_) => exchangeViewModel.wallet.walletAddresses.address, (String address) {
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
depositKey.currentState!.changeAddress(address: address);
}
if (exchangeViewModel.receiveCurrency == CryptoCurrency.xmr) {
receiveKey.currentState!.changeAddress(address: address);
}
});
_isReactionsSet = true;
}
void _onCurrencyChange(CryptoCurrency currency, ExchangeViewModel exchangeViewModel,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
key.currentState!.changeSelectedCurrency(currency);
key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
key.currentState!.changeAddress(
address: isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.address : '');
key.currentState!.changeAmount(amount: '');
}
void _onWalletNameChange(ExchangeViewModel exchangeViewModel, CryptoCurrency currency,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
if (isCurrentTypeWallet) {
key.currentState!.changeWalletName(exchangeViewModel.wallet.name);
key.currentState!.addressController.text = exchangeViewModel.wallet.walletAddresses.address;
} else if (key.currentState!.addressController.text ==
exchangeViewModel.wallet.walletAddresses.address) {
key.currentState!.changeWalletName('');
key.currentState!.addressController.text = '';
}
}
}
@@ -1,78 +0,0 @@
import 'package:flutter/material.dart';
class PickerItemWidget extends StatelessWidget {
const PickerItemWidget(
{required this.title, this.iconPath, this.isSelected = false, this.tag, this.onTap});
final String? iconPath;
final String title;
final bool isSelected;
final String? tag;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24),
child: Row(
children: [
Container(
child: Image.asset(
iconPath ?? '',
height: 20.0,
width: 20.0,
),
),
const SizedBox(width: 12),
Expanded(
child: Row(
children: [
Text(
title.toUpperCase(),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
fontSize: isSelected ? 16 : 14.0,
fontWeight: FontWeight.w600,
),
),
if (tag != null)
Align(
alignment: Alignment.topCenter,
child: Container(
width: 35.0,
height: 18.0,
child: Center(
child: Text(
tag!,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 7.0, color: Theme.of(context).colorScheme.primary),
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
//border: Border.all(color: ),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
),
),
],
),
),
if (isSelected)
Icon(
Icons.check,
color: Theme.of(context).colorScheme.primary,
),
],
),
),
),
);
}
}
@@ -1,49 +0,0 @@
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'currency_picker_item_widget.dart';
class CurrencyPickerWidget extends StatelessWidget {
CurrencyPickerWidget({
required this.crossAxisCount,
required this.selectedAtIndex,
required this.pickerItemsList,
required this.pickListItem,
});
final int crossAxisCount;
final int selectedAtIndex;
final Function pickListItem;
final List<Currency> pickerItemsList;
final ScrollController _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.outlineVariant,
child: Scrollbar(
controller: _scrollController,
child: GridView.builder(
controller: _scrollController,
padding: EdgeInsets.zero,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 2,
childAspectRatio: 3,
),
itemCount: pickerItemsList.length,
itemBuilder: (BuildContext ctx, index) {
return PickerItemWidget(
onTap: () {
pickListItem(index);
},
title: pickerItemsList[index].name,
iconPath: pickerItemsList[index].iconPath,
tag: pickerItemsList[index].tag,
);
}),
),
);
}
}
@@ -1,151 +0,0 @@
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/utils/image_utill.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/exchange/trade.dart';
class ExchangeConfirmPage extends BasePage {
ExchangeConfirmPage({required this.tradesStore}) : trade = tradesStore.trade!;
final TradesStore tradesStore;
final Trade trade;
@override
String get title => S.current.swap;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
children: <Widget>[
Expanded(
child: Column(
children: <Widget>[
Flexible(
child: Center(
child: Text(
S.of(context).exchange_result_write_down_trade_id,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontSize: 18.0,
fontWeight: FontWeight.w500,
),
),
),
),
Container(
height: 178,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
border: Border.all(
width: 0.0,
color: Theme.of(context).colorScheme.outlineVariant,
),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Column(
children: <Widget>[
Expanded(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"${trade.provider.title} ${S.of(context).trade_id}",
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
),
),
Expanded(
child: Text(
trade.id,
overflow: TextOverflow.visible,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
],
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: Builder(
builder: (context) => PrimaryButton(
key: ValueKey('exchange_confirm_page_copy_to_clipboard_button_key'),
onPressed: () {
Clipboard.setData(ClipboardData(text: trade.id));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
text: S.of(context).copy_id,
color: Theme.of(context).colorScheme.surfaceContainerLow,
textColor: Theme.of(context).colorScheme.onSurface,
borderRadius: BorderRadius.circular(30.0),
),
),
)
],
),
),
Expanded(
child: Column(
children: [
if (trade.provider == ExchangeProviderDescription.trocador)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
S.of(context).selected_trocador_provider + ':${trade.providerName}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
),
),
),
Flexible(
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
(trade.provider.image.isNotEmpty)
? ImageUtil.getImageFromPath(
imagePath: trade.provider.image, height: 50, width: 50)
: const SizedBox(),
if (!trade.provider.horizontalLogo)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(trade.provider.title),
),
],
),
),
),
],
),
),
],
),
),
PrimaryButton(
key: ValueKey('exchange_confirm_page_saved_id_button_key'),
onPressed: () => Navigator.of(context).pushReplacementNamed(Routes.exchangeTrade),
text: S.of(context).saved_the_trade_id,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
)
],
),
);
}
}
@@ -1,156 +0,0 @@
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/utils/brightness_util.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
class ExchangeTradeExternalSendPage extends BasePage {
ExchangeTradeExternalSendPage({required this.exchangeTradeViewModel});
final ExchangeTradeViewModel exchangeTradeViewModel;
@override
String get title => S.current.swap;
@override
bool get gradientAll => true;
@override
bool get resizeToAvoidBottomInset => false;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
final fetchingLabel = S.current.fetching;
@override
Widget body(BuildContext context) {
final copyImage = Image.asset(
'assets/images/copy_content.png',
height: 16,
width: 16,
color: Theme.of(context).colorScheme.primary,
);
return Container(
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Observer(
builder: (_) {
return Column(
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
color: Theme.of(context).colorScheme.surfaceContainerLow,
),
padding: EdgeInsets.fromLTRB(24, 110, 24, 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Spacer(flex: 3),
Flexible(
flex: 6,
child: GestureDetector(
onTap: () {
BrightnessUtil.changeBrightnessForFunction(
() async {
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: QrViewData(
embeddedImagePath:
exchangeTradeViewModel.trade.from?.iconPath,
data: exchangeTradeViewModel.paymentUri?.toString() ??
exchangeTradeViewModel.trade.inputAddress ??
fetchingLabel,
),
);
},
);
},
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Theme.of(context).colorScheme.primary,
),
),
child: QrImage(
data: exchangeTradeViewModel.paymentUri?.toString() ??
exchangeTradeViewModel.trade.inputAddress ??
fetchingLabel,
embeddedImagePath:
exchangeTradeViewModel.trade.from?.iconPath,
size: 230,
),
),
),
),
),
),
Spacer(flex: 3)
],
),
SizedBox(height: 24),
...exchangeTradeViewModel.items
.where((item) => item.isExternalSendDetail)
.map(
(item) => TradeItemRowWidget(
key: ValueKey(
'exchange_trade_external_send_page_send_item_${item.title}_key'),
title: item.title,
value: item.data,
isCopied: true,
copyImage: copyImage,
),
)
.toList(),
],
),
),
],
);
},
),
bottomSection: Observer(
builder: (_) {
final trade = exchangeTradeViewModel.trade;
final sendingState = exchangeTradeViewModel.sendViewModel.state;
return exchangeTradeViewModel.isSendable && !(sendingState is TransactionCommitted)
? LoadingPrimaryButton(
key: ValueKey('exchange_trade_external_send_page_continue_button_key'),
isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty,
isLoading: sendingState is IsExecutingState,
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
text: S.current.continue_text,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
)
: Offstage();
},
),
),
);
}
}
@@ -1,423 +0,0 @@
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
import 'package:cake_wallet/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/widgets/timer_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
void showInformation(ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) {
final trade = exchangeTradeViewModel.trade;
final walletName = exchangeTradeViewModel.wallet.name;
final from = trade.from?.toString() ?? '';
final information = exchangeTradeViewModel.isSendable
? S.current.exchange_trade_result_confirm(trade.amount, from, walletName) +
exchangeTradeViewModel.extraInfo
: S.current.exchange_result_description(trade.amount, from) +
exchangeTradeViewModel.extraInfo;
showPopUp<void>(
context: context,
builder: (_) => InformationPage(
key: ValueKey('information_page_dialog_key'),
information: information,
),
);
}
class ExchangeTradePage extends BasePage {
ExchangeTradePage({required this.exchangeTradeViewModel});
final ExchangeTradeViewModel exchangeTradeViewModel;
@override
String get title => S.current.swap;
@override
bool get gradientAll => true;
@override
bool get resizeToAvoidBottomInset => false;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
Widget trailing(BuildContext context) {
final questionImage = Image.asset('assets/images/question_mark.png',
color: Theme.of(context).colorScheme.onSurface);
return SizedBox(
height: 20.0,
width: 20.0,
child: ButtonTheme(
minWidth: double.minPositive,
child: TextButton(
// FIX-ME: Style
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
//padding: EdgeInsets.all(0),
onPressed: () => showInformation(exchangeTradeViewModel, context),
child: questionImage),
),
);
}
@override
Widget body(BuildContext context) => ExchangeTradeForm(exchangeTradeViewModel);
}
class ExchangeTradeForm extends StatefulWidget {
ExchangeTradeForm(this.exchangeTradeViewModel);
final ExchangeTradeViewModel exchangeTradeViewModel;
@override
ExchangeTradeState createState() => ExchangeTradeState();
}
class ExchangeTradeState extends State<ExchangeTradeForm> {
final fetchingLabel = S.current.fetching;
String get title => S.current.exchange;
bool _effectsInstalled = false;
ReactionDisposer? _exchangeStateReaction;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
}
void afterLayout(dynamic _) {
showInformation(widget.exchangeTradeViewModel, context);
}
@override
void dispose() {
widget.exchangeTradeViewModel.timer?.cancel();
_exchangeStateReaction?.reaction.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_setEffects();
return Container(
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 16),
content: Observer(builder: (_) {
final trade = widget.exchangeTradeViewModel.trade;
return Column(
children: <Widget>[
trade.expiredAt != null
? Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
S.of(context).offer_expires_in,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
if (trade.expiredAt != null)
TimerWidget(trade.expiredAt!,
color: Theme.of(context).colorScheme.onSurface)
])
: Offstage(),
_ExchangeTradeItemsCardSection(
viewModel: widget.exchangeTradeViewModel,
),
],
);
}),
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
bottomSection: Column(
children: [
if (!widget.exchangeTradeViewModel.shouldHideExternalSendButton)
PrimaryButton(
key: ValueKey('exchange_trade_page_send_from_external_button_key'),
text: S.current.send_from_external_wallet,
onPressed: () async {
Navigator.of(context).pushNamed(Routes.exchangeTradeExternalSendPage);
},
color: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.primary,
textColor: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.onPrimary,
),
SizedBox(height: 16),
Observer(
builder: (_) {
final trade = widget.exchangeTradeViewModel.trade;
final sendingState = widget.exchangeTradeViewModel.sendViewModel.state;
return Offstage(
offstage: !(widget.exchangeTradeViewModel.isSendable &&
!(sendingState is TransactionCommitted)),
child: LoadingPrimaryButton(
key: ValueKey('exchange_trade_page_send_from_cake_button_key'),
isDisabled: trade.inputAddress == null ||
trade.inputAddress!.isEmpty ||
sendingState is ExecutedSuccessfullyState,
isLoading: sendingState is IsExecutingState,
onPressed: _onPressedSendFromCakeWallet,
text: S.current.send_from_cake_wallet,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
);
},
),
],
),
),
);
}
Future<void> _onPressedSendFromCakeWallet() async {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
if (sendVM.wallet.isHardwareWallet) {
if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendVM.walletType,
hardwareWalletType: sendVM.wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (context, _) {
sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet);
Navigator.of(context).pop();
},
));
} else {
sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet);
}
}
widget.exchangeTradeViewModel.confirmSending();
}
BuildContext? dialogContext;
BuildContext? loadingBottomSheetContext;
void _setEffects() {
if (_effectsInstalled) {
return;
}
_exchangeStateReaction = reaction(
(_) => this.widget.exchangeTradeViewModel.sendViewModel.state,
(ExecutionState state) async {
if (dialogContext != null && dialogContext?.mounted == true) {
Navigator.of(dialogContext!).pop();
}
if (state is! IsExecutingState &&
loadingBottomSheetContext != null &&
loadingBottomSheetContext!.mounted) {
Navigator.of(loadingBottomSheetContext!).pop();
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
key: ValueKey('exchange_trade_page_send_failure_dialog_key'),
buttonKey: ValueKey('exchange_trade_page_send_failure_dialog_button_key'),
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
});
}
if (state is IsExecutingState) {
// wait a bit to avoid showing the loading dialog if transaction is failed
await Future.delayed(const Duration(milliseconds: 300));
final currentState = widget.exchangeTradeViewModel.sendViewModel.state;
if (currentState is ExecutedSuccessfullyState || currentState is FailureState) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext context) {
loadingBottomSheetContext = context;
return LoadingBottomSheet(
titleText: S.of(context).generating_transaction,
);
},
);
}
});
}
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final trade = widget.exchangeTradeViewModel.trade;
final isSwapsXyz = trade.provider == ExchangeProviderDescription.swapsXyz;
final isEVMWallet = widget.exchangeTradeViewModel.sendViewModel.isEVMWallet;
final amountValue = isSwapsXyz &&
isEVMWallet &&
!widget.exchangeTradeViewModel.isSwapsXYZCanSendFromExternal
? trade.amount
: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.amountFormatted;
final fiatAmountValue = isSwapsXyz &&
isEVMWallet &&
!widget.exchangeTradeViewModel.isSwapsXYZCanSendFromExternal
? widget.exchangeTradeViewModel.sendViewModel
.calculateTransactionFiatAmount(amountValue)
: widget.exchangeTradeViewModel.sendViewModel.pendingTransactionFiatAmountFormatted;
if (context.mounted) {
final result = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
isScrollControlled: true,
builder: (bottomSheetContext) {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
return ConfirmSendingBottomSheet(
key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
footerType: FooterType.slideActionButton,
isSlideActionEnabled: sendVM.isReadyForSend,
walletType: sendVM.walletType,
titleText: S.of(bottomSheetContext).confirm_transaction,
titleIconPath: sendVM.selectedCryptoCurrency.iconPath,
currency: widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue: sendVM.amountParsingProxy
.getDisplayCryptoAmount(amountValue, sendVM.selectedCryptoCurrency),
explanation: sendVM.pendingTransactionAdditionalCostNotice,
fiatAmountValue: fiatAmountValue,
fee: isEVMCompatibleChain(sendVM.walletType)
? S.of(bottomSheetContext).send_estimated_fee
: S.of(bottomSheetContext).send_fee,
feeValue: sendVM.amountParsingProxy
.asDisplayStringWithSymbol(sendVM.pendingTransaction!.fee),
feeFiatAmount: sendVM.pendingTransactionFeeFiatAmountFormatted,
outputs: sendVM.outputs,
onSlideActionComplete: () async {
if (bottomSheetContext.mounted && Navigator.canPop(bottomSheetContext))
Navigator.of(bottomSheetContext).pop(true);
sendVM.commitTransaction(context);
},
amountParsingProxy: sendVM.amountParsingProxy,
);
},
);
if (result == null) widget.exchangeTradeViewModel.sendViewModel.dismissTransaction();
}
});
}
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback(
(_) async {
if (!mounted) return;
await showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) {
return InfoBottomSheet(
footerType: FooterType.singleActionButton,
titleText: S.of(bottomSheetContext).transaction_sent,
contentImage: 'assets/images/birthday_cake.png',
singleActionButtonText: S.of(bottomSheetContext).close,
singleActionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
onSingleActionButtonPressed: () {
Navigator.of(bottomSheetContext).pop();
if (mounted) {
Navigator.of(context).pushNamedAndRemoveUntil(
Routes.dashboard,
(route) => false,
);
}
RequestReviewHandler.requestReview();
},
);
},
);
},
);
}
},
);
_effectsInstalled = true;
}
}
class _ExchangeTradeItemsCardSection extends StatelessWidget {
const _ExchangeTradeItemsCardSection({required this.viewModel});
final ExchangeTradeViewModel viewModel;
@override
Widget build(BuildContext context) {
final firstExchangeCard = ExchangeTradeCardItemWidget(
isReceiveDetailsCard: true,
exchangeTradeViewModel: viewModel,
);
final secondExchangeCard = ExchangeTradeCardItemWidget(
isReceiveDetailsCard: false,
exchangeTradeViewModel: viewModel,
);
if (responsiveLayoutUtil.shouldRenderMobileUI) {
return MobileExchangeCardsSection(
firstExchangeCard: firstExchangeCard,
secondExchangeCard: secondExchangeCard,
);
}
return DesktopExchangeCardsSection(
firstExchangeCard: firstExchangeCard,
secondExchangeCard: secondExchangeCard,
);
}
}
@@ -1,47 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
class InformationPage extends StatelessWidget {
InformationPage({required this.information, super.key});
final String information;
@override
Widget build(BuildContext context) {
return AlertBackground(
child: Center(
child: Container(
margin: EdgeInsets.only(left: 24, right: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
color: Theme.of(context).colorScheme.surface,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(24, 28, 24, 24),
child: Text(
information,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: PrimaryButton(
key: ValueKey('information_page_got_it_button_key'),
onPressed: () => Navigator.of(context).pop(),
text: S.of(context).got_it,
color: Theme.of(context).colorScheme.surfaceContainer,
textColor: Theme.of(context).colorScheme.onSurface,
),
)
],
),
),
));
}
}
@@ -1,290 +0,0 @@
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class ExchangeTradeCardItemWidget extends StatelessWidget {
ExchangeTradeCardItemWidget({
required this.isReceiveDetailsCard,
required this.exchangeTradeViewModel,
Key? key,
}) : feesViewModel = exchangeTradeViewModel.feesViewModel,
output = exchangeTradeViewModel.output;
final Output output;
final bool isReceiveDetailsCard;
final FeesViewModel feesViewModel;
final ExchangeTradeViewModel exchangeTradeViewModel;
@override
Widget build(BuildContext context) {
final copyImage = Image.asset(
'assets/images/copy_content.png',
height: 16,
width: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
return Container(
width: double.infinity,
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 10),
...exchangeTradeViewModel.items
.where((item) => item.isReceiveDetail == isReceiveDetailsCard)
.map(
(item) => TradeItemRowWidget(
title: item.title,
value: item.data,
isCopied: item.isCopied,
copyImage: copyImage,
),
)
.toList(),
if (!isReceiveDetailsCard && exchangeTradeViewModel.isSendable) ...[
if (feesViewModel.hasFees)
FeeSelectionWidget(
feesViewModel: feesViewModel,
output: output,
onTap: () => pickTransactionPriority(context, output),
),
if (exchangeTradeViewModel.sendViewModel.hasCoinControl)
CoinControlWidget(
onTap: () => Navigator.of(context).pushNamed(
Routes.unspentCoinsList,
arguments: exchangeTradeViewModel.sendViewModel.coinTypeToSpendFrom,
),
),
],
],
),
);
}
Future<void> pickTransactionPriority(BuildContext context, Output output) async {
final items = priorityForWalletType(feesViewModel.walletType);
final selectedItem = items.indexOf(feesViewModel.transactionPriority);
final customItemIndex = feesViewModel.getCustomPriorityIndex(items);
final isBitcoinWallet = feesViewModel.walletType == WalletType.bitcoin;
final maxCustomFeeRate = feesViewModel.maxCustomFeeRate?.toDouble();
double? customFeeRate = isBitcoinWallet ? feesViewModel.customBitcoinFeeRate.toDouble() : null;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
int selectedIdx = selectedItem;
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Picker(
items: items,
displayItem: (TransactionPriority priority) =>
feesViewModel.displayFeeRate(priority, customFeeRate?.round()),
selectedAtIndex: selectedIdx,
customItemIndex: customItemIndex,
maxValue: maxCustomFeeRate,
title: S.of(context).please_select,
headerEnabled: !isBitcoinWallet,
closeOnItemSelected: !isBitcoinWallet,
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: customFeeRate,
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
onItemSelected: (TransactionPriority priority) async {
feesViewModel.setTransactionPriority(priority);
setState(() => selectedIdx = items.indexOf(priority));
await output.calculateEstimatedFee();
if (feesViewModel.isLowFee) {
_showFeeAlert(context);
}
},
);
},
);
},
);
if (isBitcoinWallet) {
feesViewModel.customBitcoinFeeRate = customFeeRate!.round();
if (feesViewModel.showAlertForCustomFeeRate()) {
_showFeeAlert(context);
}
}
}
void _showFeeAlert(BuildContext context) async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).low_fee,
alertContent: S.of(context).low_fee_alert,
leftButtonText: S.of(context).ignor,
rightButtonText: S.of(context).use_suggested,
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
actionRightButton: () => Navigator.of(dialogContext).pop(true));
}) ??
false;
if (confirmed) {
feesViewModel.setDefaultTransactionPriority();
}
}
}
class TradeItemRowWidget extends StatelessWidget {
final String title;
final String value;
final bool isCopied;
final Image copyImage;
const TradeItemRowWidget({
required this.title,
required this.value,
required this.isCopied,
required this.copyImage,
super.key,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: GestureDetector(
onTap: () {
if (!isCopied) return;
Clipboard.setData(ClipboardData(text: value));
showBar<void>(context, S.of(context).transaction_details_copied(title));
},
child: ListRow(
padding: EdgeInsets.zero,
title: title,
value: value,
image: isCopied ? copyImage : null,
color: Colors.transparent,
hintTextColor: Theme.of(context).colorScheme.onSurfaceVariant,
mainTextColor: Theme.of(context).colorScheme.onSurface,
),
),
);
}
}
class FeeSelectionWidget extends StatelessWidget {
final FeesViewModel feesViewModel;
final Output output;
final VoidCallback onTap;
const FeeSelectionWidget({
required this.feesViewModel,
required this.output,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Observer(
builder: (_) => GestureDetector(
key: ValueKey('exchange_trade_card_item_widget_select_fee_priority_button_key'),
onTap: feesViewModel.hasFeesPriority ? onTap : () {},
child: Container(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).send_estimated_fee,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${output.estimatedFee} ${feesViewModel.amountParsingProxy.getCryptoSymbol(feesViewModel.currency)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
if (!feesViewModel.isFiatDisabled)
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'${output.estimatedFeeFiatAmount} ${feesViewModel.fiat.title}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
],
),
),
),
);
}
}
class CoinControlWidget extends StatelessWidget {
final VoidCallback onTap;
const CoinControlWidget({required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
key: ValueKey('exchange_trade_card_item_widget_unspent_coin_button_key'),
onTap: onTap,
child: Container(
color: Colors.transparent,
padding: EdgeInsets.only(top: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).coin_control,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
],
),
),
);
}
}
@@ -1,79 +0,0 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:cake_wallet/generated/i18n.dart';
class TimerWidget extends StatefulWidget {
TimerWidget(this.expiratedAt, {this.color = Colors.black});
final DateTime expiratedAt;
final Color color;
@override
TimerWidgetState createState() => TimerWidgetState();
}
class TimerWidgetState extends State<TimerWidget> {
TimerWidgetState()
: _leftSeconds = 0,
_minutes = 0,
_seconds = 0,
_isExpired = false;
int _leftSeconds;
int _minutes;
int _seconds;
bool _isExpired;
Timer? _timer;
@override
void initState() {
super.initState();
final start = DateTime.now();
_isExpired = false;
_leftSeconds = widget.expiratedAt.difference(start).inSeconds;
_recalculate();
WidgetsBinding.instance.addPostFrameCallback((_) {
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
if (_isExpired) {
timer.cancel();
}
_leftSeconds--;
_isExpired = _leftSeconds <= 0;
_recalculate();
setState(() {});
});
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return _isExpired
? Text(
S.of(context).expired,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.errorContainer,
),
)
: Text(
S.of(context).time(_minutes.toString(), _seconds.toString()),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: widget.color,
),
);
}
void _recalculate() {
_minutes = _leftSeconds ~/ 60;
_seconds = _leftSeconds % 60;
}
}
-52
View File
@@ -1,52 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/faq/faq_item.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/store/settings_store.dart';
class FaqPage extends BasePage {
FaqPage(this.settingsStore);
final SettingsStore settingsStore;
@override
String get title => S.current.faq;
String get path => 'assets/faq/faq_' + settingsStore.languageCode + '.json';
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 12, left: 24),
child: FutureBuilder(
builder: (context, snapshot) {
final faqItems = jsonDecode(snapshot.data.toString()) as List;
return SingleChildScrollView(
child: Column(
children: <Widget>[
StandardListSeparator(),
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
final title = faqItems[index]["question"].toString();
final text = faqItems[index]["answer"].toString();
return FAQItem(title, text);
},
separatorBuilder: (_, __) => StandardListSeparator(),
itemCount: faqItems?.length ?? 0,
)
],
),
);
},
future: rootBundle.loadString(path),
),
);
}
}
@@ -1,69 +0,0 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/monero_account_label_validator.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class MoneroAccountEditOrCreatePage extends BasePage {
MoneroAccountEditOrCreatePage({required this.moneroAccountCreationViewModel})
: _formKey = GlobalKey<FormState>(),
_textController = TextEditingController() {
_textController.addListener(() => moneroAccountCreationViewModel.label = _textController.text);
_textController.text = moneroAccountCreationViewModel.label;
}
final MoneroAccountEditOrCreateViewModel moneroAccountCreationViewModel;
@override
String get title => S.current.account;
final GlobalKey<FormState> _formKey;
final TextEditingController _textController;
@override
Widget body(BuildContext context) => Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _textController,
hintText: S.of(context).account,
validator: MoneroLabelValidator(),
),
),
),
Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await moneroAccountCreationViewModel.save();
if (context.mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop(_textController.text);
}
},
text: moneroAccountCreationViewModel.isEdit
? S.of(context).rename
: S.of(context).add,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: moneroAccountCreationViewModel.state is IsExecutingState,
isDisabled: moneroAccountCreationViewModel.label?.isEmpty ?? true,
),
)
],
),
),
);
}
@@ -1,95 +0,0 @@
import 'package:cake_wallet/src/widgets/picker_inner_wrapper_widget.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
import 'package:cake_wallet/src/screens/monero_accounts/widgets/account_tile.dart';
class MoneroAccountListPage extends StatelessWidget {
MoneroAccountListPage({required this.accountListViewModel});
final MoneroAccountListViewModel accountListViewModel;
final ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
double itemHeight = 65;
double buttonHeight = 62;
return Observer(builder: (_) {
final accounts = accountListViewModel.accounts;
return PickerInnerWrapperWidget(
title: S.of(context).choose_account,
itemsHeight: (itemHeight * accounts.length) + buttonHeight,
children: [
Expanded(
child: Scrollbar(
controller: controller,
child: ListView.separated(
padding: EdgeInsets.zero,
controller: controller,
separatorBuilder: (context, index) => const HorizontalSectionDivider(),
itemCount: accounts.length,
itemBuilder: (context, index) {
final account = accounts[index];
return AccountTile(
isCurrent: account.isSelected,
accountName: account.label,
accountBalance: account.balance ?? '0.00',
currency: accountListViewModel.currency.toString(),
onTap: () {
if (account.isSelected) {
return;
}
accountListViewModel.select(account);
Navigator.of(context).pop();
},
onEdit: () async => await Navigator.of(context).pushNamed(
Routes.accountCreation,
arguments: account,
),
);
},
),
)),
GestureDetector(
onTap: () async => await Navigator.of(context).pushNamed(Routes.accountCreation),
child: Container(
height: buttonHeight,
color: Theme.of(context).colorScheme.primary,
padding: EdgeInsets.symmetric(horizontal: 24),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.add,
color: Theme.of(context).colorScheme.onPrimary,
),
Padding(
padding: EdgeInsets.only(left: 5),
child: Text(
S.of(context).create_new_account,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onPrimary,
decoration: TextDecoration.none,
),
),
)
],
),
),
),
)
],
);
});
}
}
@@ -1,96 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/generated/i18n.dart';
class AccountTile extends StatelessWidget {
AccountTile({
required this.isCurrent,
required this.accountName,
this.accountBalance,
required this.currency,
required this.onTap,
required this.onEdit,
});
final bool isCurrent;
final String accountName;
final String? accountBalance;
final String currency;
final Function() onTap;
final Function() onEdit;
@override
Widget build(BuildContext context) {
final color = isCurrent
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.surfaceContainerHighest;
final textColor = isCurrent
? Theme.of(context).colorScheme.onSurface
: Theme.of(context).colorScheme.onSurfaceVariant;
final Widget cell = GestureDetector(
onTap: onTap,
child: Container(
height: 60,
width: double.infinity,
padding: EdgeInsets.only(left: 24, right: 24),
color: color,
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceBetween,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
child: Text(
accountName,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w600,
color: textColor,
decoration: TextDecoration.none,
),
),
),
if (accountBalance != null)
Container(
child: Text(
'${accountBalance.toString()} $currency',
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15,
fontWeight: FontWeight.w600,
color: isCurrent
? Theme.of(context).colorScheme.onSurface
: Theme.of(context).colorScheme.onSurfaceVariant,
decoration: TextDecoration.none,
),
),
),
],
),
),
);
// return cell;
return Slidable(
key: Key(accountName),
child: cell,
endActionPane: _actionPane(context),
);
}
ActionPane _actionPane(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
children: [
SlidableAction(
onPressed: (_) => onEdit.call(),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
icon: Icons.edit,
label: S.of(context).edit,
),
],
);
}
@@ -1,69 +0,0 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/monero_account_label_validator.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class NanoAccountEditOrCreatePage extends BasePage {
NanoAccountEditOrCreatePage({required this.nanoAccountCreationViewModel})
: _formKey = GlobalKey<FormState>(),
_textController = TextEditingController() {
_textController.addListener(() => nanoAccountCreationViewModel.label = _textController.text);
_textController.text = nanoAccountCreationViewModel.label;
}
final NanoAccountEditOrCreateViewModel nanoAccountCreationViewModel;
@override
String get title => S.current.account;
final GlobalKey<FormState> _formKey;
final TextEditingController _textController;
@override
Widget body(BuildContext context) => Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _textController,
hintText: S.of(context).account,
validator: MoneroLabelValidator(),
),
),
),
Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await nanoAccountCreationViewModel.save();
Navigator.of(context).pop(_textController.text);
},
text: nanoAccountCreationViewModel.isEdit
? S.of(context).rename
: S.of(context).add,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: nanoAccountCreationViewModel.state is IsExecutingState,
isDisabled: nanoAccountCreationViewModel.label?.isEmpty ?? true,
),
),
],
),
),
);
}
@@ -1,92 +0,0 @@
import 'package:cake_wallet/src/widgets/picker_inner_wrapper_widget.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/monero_accounts/widgets/account_tile.dart';
class NanoAccountListPage extends StatelessWidget {
NanoAccountListPage({required this.accountListViewModel});
final NanoAccountListViewModel accountListViewModel;
final ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
double itemHeight = 80;
double buttonHeight = 62;
return Observer(builder: (_) {
final accounts = accountListViewModel.accounts;
return PickerInnerWrapperWidget(
title: S.of(context).choose_account,
itemsHeight: (itemHeight * accounts.length) + buttonHeight,
children: [
Expanded(
child: Scrollbar(
controller: controller,
child: ListView.separated(
padding: EdgeInsets.zero,
controller: controller,
separatorBuilder: (context, index) => const VerticalSectionDivider(),
itemCount: accounts.length,
itemBuilder: (context, index) {
final account = accounts[index];
return AccountTile(
isCurrent: account.isSelected,
accountName: account.label,
accountBalance: account.balance ?? '0.00',
currency: accountListViewModel.currency.toString(),
onTap: () {
if (account.isSelected) {
return;
}
accountListViewModel.select(account);
Navigator.of(context).pop();
},
onEdit: () async => await Navigator.of(context)
.pushNamed(Routes.nanoAccountCreation, arguments: account),
);
},
),
)),
GestureDetector(
onTap: () async => await Navigator.of(context).pushNamed(Routes.nanoAccountCreation),
child: Container(
height: buttonHeight,
color: Theme.of(context).colorScheme.primary,
padding: EdgeInsets.symmetric(horizontal: 24),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.add,
color: Theme.of(context).colorScheme.onPrimary,
),
Padding(
padding: EdgeInsets.only(left: 5),
child: Text(
S.of(context).create_new_account,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 15,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
),
),
)
],
),
),
),
)
],
);
});
}
}
@@ -1,89 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/generated/i18n.dart';
class AccountTile extends StatelessWidget {
AccountTile(
{required this.isCurrent,
required this.accountName,
this.accountBalance,
required this.currency,
required this.onTap,
required this.onEdit});
final bool isCurrent;
final String accountName;
final String? accountBalance;
final String currency;
final Function() onTap;
final Function() onEdit;
@override
Widget build(BuildContext context) {
final color = isCurrent
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerHighest;
final textColor = isCurrent
? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onSurfaceVariant;
final Widget cell = GestureDetector(
onTap: onTap,
child: Container(
height: 77,
width: double.infinity,
padding: EdgeInsets.only(left: 24, right: 24),
color: color,
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceBetween,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
child: Text(
accountName,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w600,
color: textColor,
decoration: TextDecoration.none,
),
),
),
if (accountBalance != null)
Container(
child: Text(
'${accountBalance.toString()} $currency',
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Theme.of(context).textTheme.headlineMedium!.color!,
decoration: TextDecoration.none,
),
),
),
],
),
),
);
// return cell;
return Slidable(key: Key(accountName), child: cell, endActionPane: _actionPane(context));
}
ActionPane _actionPane(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
children: [
SlidableAction(
onPressed: (_) => onEdit.call(),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
icon: Icons.edit,
label: S.of(context).edit,
),
],
);
}
@@ -1,20 +0,0 @@
import 'package:cake_wallet/themes/core/custom_theme_colors.dart';
import 'package:flutter/material.dart';
class NodeIndicator extends StatelessWidget {
const NodeIndicator({this.isLive = false});
final bool isLive;
@override
Widget build(BuildContext context) {
return Container(
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isLive ? CustomThemeColors.syncGreen : Theme.of(context).colorScheme.errorContainer,
),
);
}
}
@@ -1,46 +0,0 @@
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class AddressListPage extends StatelessWidget {
AddressListPage(this.addressListViewModel);
final WalletAddressListViewModel addressListViewModel;
@override
String get title => S.current.accounts_subaddresses;
@override
Widget build(BuildContext context) {
return Column(
children: [
ModalTopBar(
title: S.of(context).accounts_subaddresses,
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
child: Column(
children: <Widget>[
AddressList(
addressListViewModel: addressListViewModel,
onSelect: (String address) async {
Navigator.of(context).pop(address);
},
),
],
),
),
),
],
);
}
}
@@ -1,253 +0,0 @@
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AnonPayInvoicePage extends BasePage {
AnonPayInvoicePage(
this.anonInvoicePageViewModel,
this.receiveOptionViewModel,
) : _amountFocusNode = FocusNode() {}
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _descriptionController = TextEditingController();
final _amountController = TextEditingController();
final FocusNode _amountFocusNode;
final AnonInvoicePageViewModel anonInvoicePageViewModel;
final ReceiveOptionViewModel receiveOptionViewModel;
final _formKey = GlobalKey<FormState>();
bool effectsInstalled = false;
@override
bool get gradientAll => true;
@override
bool get resizeToAvoidBottomInset => false;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle =>
FeatureFlag.hasNewUiExtraPages ? AppBarStyle.completelyTransparent : AppBarStyle.transparent;
@override
void onClose(BuildContext context) => FeatureFlag.hasNewUiExtraPages
? Navigator.of(context, rootNavigator: true).pop()
: Navigator.popUntil(context, (route) => route.isFirst);
@override
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
receiveOptionViewModel: receiveOptionViewModel, color: titleColor(context));
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).clear,
onPressed: () {
_formKey.currentState?.reset();
anonInvoicePageViewModel.reset();
});
Future<bool> _onNavigateBack(BuildContext context) async {
onClose(context);
return false;
}
@override
Widget body(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context));
return WillPopScope(
onWillPop: () => _onNavigateBack(context),
child: KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).colorScheme.surfaceVariant,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
],
),
child: Container(
color: Theme.of(context).colorScheme.surface,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: responsiveLayoutUtil.shouldRenderMobileUI
? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
color: Theme.of(context).colorScheme.surfaceContainerLow,
)
: null,
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
child: AnonInvoiceForm(
nameController: _nameController,
descriptionController: _descriptionController,
amountController: _amountController,
emailController: _emailController,
depositAmountFocus: _amountFocusNode,
formKey: _formKey,
isInvoice: receiveOptionViewModel.selectedReceiveOption ==
ReceivePageOption.anonPayInvoice,
anonInvoicePageViewModel: anonInvoicePageViewModel,
),
);
}),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(builder: (_) {
final isInvoice =
receiveOptionViewModel.selectedReceiveOption == ReceivePageOption.anonPayInvoice;
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Center(
child: Text(
isInvoice
? S.of(context).anonpay_description("an invoice", "pay")
: S.of(context).anonpay_description("a donation link", "donate"),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
),
),
LoadingPrimaryButton(
text: isInvoice
? S.of(context).create_invoice
: S.of(context).create_donation_link,
onPressed: () {
FocusScope.of(context).unfocus();
anonInvoicePageViewModel.setRequestParams(
inputAmount: _amountController.text,
inputName: _nameController.text,
inputEmail: _emailController.text,
inputDescription: _descriptionController.text,
);
if (anonInvoicePageViewModel.receipientEmail.isNotEmpty &&
_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
return;
}
if (isInvoice) {
anonInvoicePageViewModel.createInvoice();
} else {
anonInvoicePageViewModel.generateDonationLink();
}
},
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: anonInvoicePageViewModel.state is IsExecutingState,
),
],
);
}),
),
),
),
);
}
void _setReactions(BuildContext context) {
if (effectsInstalled) {
return;
}
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
switch (option) {
case ReceivePageOption.mainnet:
Navigator.popAndPushNamed(context, Routes.addressPage);
break;
case ReceivePageOption.anonPayDonationLink:
final sharedPreferences = getIt.get<SharedPreferences>();
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
final donationWalletName =
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
if (clearnetUrl != null &&
onionUrl != null &&
anonInvoicePageViewModel.currentWalletName == donationWalletName) {
Navigator.pushReplacementNamed(
context,
Routes.anonPayReceivePage,
arguments: AnonPayReceivePageArgs(
invoiceInfo: AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl,
onionUrl: onionUrl,
address: anonInvoicePageViewModel.address,
),
qrImage: anonInvoicePageViewModel.qrImage,
),
);
}
break;
default:
}
});
reaction((_) => anonInvoicePageViewModel.state, (ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
Navigator.pushNamed(
context,
Routes.anonPayReceivePage,
arguments: AnonPayReceivePageArgs(
invoiceInfo: state.payload as AnonpayInvoiceInfo,
qrImage: anonInvoicePageViewModel.qrImage,
),
);
}
if (state is FailureState) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
});
effectsInstalled = true;
}
}
@@ -1,158 +0,0 @@
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/screens/receive/widgets/anonpay_status_section.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/screens/receive/widgets/copy_link_item.dart';
import 'package:cake_wallet/utils/brightness_util.dart';
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart' as qr;
class AnonPayReceivePageArgs {
final AnonpayInfoBase invoiceInfo;
final String? qrImage;
AnonPayReceivePageArgs({required this.invoiceInfo, this.qrImage});
}
class AnonPayReceivePage extends BasePage {
AnonPayReceivePage({required this.args}) : invoiceInfo = args.invoiceInfo;
final AnonPayReceivePageArgs args;
final AnonpayInfoBase invoiceInfo;
@override
String get title => S.current.receive;
@override
bool get gradientBackground => true;
@override
bool get resizeToAvoidBottomInset => false;
@override
void onClose(BuildContext context) => Navigator.popUntil(context, (route) => route.isFirst);
@override
Widget middle(BuildContext context) {
return Column(
children: [
Text(
title,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 18.0,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Text(
invoiceInfo is AnonpayInvoiceInfo
? ReceivePageOption.anonPayInvoice.toString()
: ReceivePageOption.anonPayDonationLink.toString(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
);
}
@override
Widget? trailing(BuildContext context) {
if (invoiceInfo is AnonpayInvoiceInfo) {
return null;
}
return Material(
color: Colors.transparent,
child: IconButton(
onPressed: () => Navigator.popAndPushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [invoiceInfo.address, ReceivePageOption.anonPayDonationLink],
),
icon: Icon(
Icons.edit,
color: pageIconColor(context),
size: 22.0,
),
),
);
}
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: 24),
if (invoiceInfo is AnonpayInvoiceInfo)
AnonInvoiceStatusSection(invoiceInfo: invoiceInfo as AnonpayInvoiceInfo),
Padding(
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.5,
),
child: GestureDetector(
onTap: () async {
BrightnessUtil.changeBrightnessForFunction(() async {
await Navigator.pushNamed(context, Routes.fullscreenQR,
arguments: QrViewData(
data: invoiceInfo.clearnetUrl,
version: qr.QrVersions.auto,
embeddedImagePath: args.qrImage,
));
});
},
child: Hero(
tag: Key(invoiceInfo.clearnetUrl),
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
child: QrImage(
data: invoiceInfo.clearnetUrl,
version: qr.QrVersions.auto,
embeddedImagePath: args.qrImage,
size: 230,
),
),
),
),
),
),
),
),
SizedBox(height: 24),
Column(
children: [
CopyLinkItem(url: invoiceInfo.clearnetUrl, title: S.of(context).clearnet_link),
SizedBox(height: 16),
CopyLinkItem(url: invoiceInfo.onionUrl, title: S.of(context).onion_link),
],
),
SizedBox(height: 100),
],
),
);
}
}
-125
View File
@@ -1,125 +0,0 @@
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
class ReceivePage extends BasePage {
ReceivePage({required this.addressListViewModel})
: _cryptoAmountFocus = FocusNode(),
_amountController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
_amountController.addListener(() {
if (_formKey.currentState!.validate()) {
addressListViewModel.changeAmount(_amountController.text);
}
});
}
final WalletAddressListViewModel addressListViewModel;
final TextEditingController _amountController;
final GlobalKey<FormState> _formKey;
static const _heroTag = 'receive_page';
@override
String get title => S.current.receive;
@override
bool get gradientBackground => true;
@override
bool get resizeToAvoidBottomInset => true;
final FocusNode _cryptoAmountFocus;
@override
Widget middle(BuildContext context) {
return Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontSize: 18.0,
fontWeight: FontWeight.bold,
color: pageIconColor(context),
),
);
}
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
@override
Widget trailing(BuildContext context) {
return Material(
color: Colors.transparent,
child: Semantics(
label: S.of(context).share,
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => ShareUtil.share(
text: addressListViewModel.uri.toString(),
context: context,
),
icon: Icon(
Icons.share,
size: 20,
color: pageIconColor(context),
),
),
),
);
}
@override
Widget body(BuildContext context) => KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).colorScheme.surface,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
child: QRWidget(
addressListViewModel: addressListViewModel,
formKey: _formKey,
heroTag: _heroTag,
amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController,
),
),
AddressList(addressListViewModel: addressListViewModel),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(
addressListViewModel.isSilentPayments
? S.of(context).silent_payments_disclaimer
: S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
),
);
}
@@ -1,231 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class AddressCell extends StatelessWidget {
AddressCell({
required this.address,
required this.name,
required this.isCurrent,
required this.isPrimary,
required this.backgroundColor,
required this.textColor,
required this.walletType,
this.onTap,
this.onEdit,
this.onHide,
this.isHidden = false,
this.onDelete,
this.txCount,
this.balance,
this.isChange = false,
this.hasBalance = false,
this.hasReceived = false,
});
factory AddressCell.fromItem(
WalletAddressListItem item, {
required bool isCurrent,
required Color backgroundColor,
required Color textColor,
required WalletType walletType,
Function(String)? onTap,
bool hasBalance = false,
bool hasReceived = false,
Function()? onEdit,
Function()? onHide,
bool isHidden = false,
Function()? onDelete,
}) =>
AddressCell(
address: item.address,
name: item.name ?? '',
isCurrent: isCurrent,
isPrimary: item.isPrimary,
backgroundColor: backgroundColor,
textColor: textColor,
walletType: walletType,
onTap: onTap,
onEdit: onEdit,
onHide: onHide,
isHidden: isHidden,
onDelete: onDelete,
txCount: item.txCount,
balance: item.balance,
isChange: item.isChange,
hasBalance: hasBalance,
hasReceived: hasReceived,
);
final String address;
final String name;
final bool isCurrent;
final bool isPrimary;
final Color backgroundColor;
final Color textColor;
final WalletType walletType;
final Function(String)? onTap;
final Function()? onEdit;
final Function()? onHide;
final bool isHidden;
final Function()? onDelete;
final int? txCount;
final String? balance;
final bool isChange;
final bool hasBalance;
final bool hasReceived;
@override
Widget build(BuildContext context) {
final Widget cell = InkWell(
onTap: () => onTap?.call(address),
child: Container(
width: double.infinity,
color: backgroundColor,
padding: EdgeInsets.only(left: 24, right: 24, top: 20, bottom: 20),
child: Row(
children: [
Expanded(
child: Column(
children: [
Row(
mainAxisAlignment: name.isNotEmpty
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Row(
children: [
if (isChange)
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Container(
height: 20,
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: textColor,
),
alignment: Alignment.center,
child: Text(
S.of(context).unspent_change,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: backgroundColor,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),
),
),
if (name.isNotEmpty)
Text(
'$name',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: textColor,
),
),
],
),
Flexible(
child: AddressFormatter.buildSegmentedAddress(
address: address,
walletType: walletType,
shouldTruncate: name.isNotEmpty || address.length > 43,
evenTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: isChange ? 10 : 14,
color: textColor,
),
),
),
],
),
if (hasBalance || hasReceived)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Text(
'${hasReceived ? S.of(context).received : S.of(context).balance}: $balance',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
color: textColor,
),
),
Text(
'${S.of(context).transactions.toLowerCase()}: $txCount',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
color: textColor,
),
),
],
),
),
],
),
),
],
),
));
return onEdit == null
? cell
: Semantics(
label: S.of(context).slidable,
selected: isCurrent,
enabled: !isCurrent,
child: Slidable(
key: Key(address),
startActionPane: _actionPaneStart(context),
endActionPane: _actionPaneEnd(context),
child: cell,
),
);
}
ActionPane _actionPaneEnd(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: onDelete != null ? 0.4 : 0.3,
children: [
SlidableAction(
onPressed: (_) => onHide?.call(),
backgroundColor: isHidden
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.errorContainer,
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
icon: isHidden ? CupertinoIcons.arrow_left : CupertinoIcons.arrow_right,
label: isHidden ? S.of(context).show : S.of(context).hide,
),
],
);
ActionPane _actionPaneStart(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: onDelete != null ? 0.4 : 0.3,
children: [
SlidableAction(
onPressed: (_) => onEdit?.call(),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
icon: Icons.edit,
label: S.of(context).edit,
),
if (onDelete != null)
SlidableAction(
onPressed: (_) => onDelete!.call(),
backgroundColor: Theme.of(context).colorScheme.errorContainer,
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
icon: Icons.delete,
label: S.of(context).delete,
),
],
);
}
@@ -1,209 +0,0 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/list_item.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class AddressList extends StatefulWidget {
const AddressList({super.key, required this.addressListViewModel, this.onSelect});
final WalletAddressListViewModel addressListViewModel;
final Function(String)? onSelect;
@override
State<AddressList> createState() => _AddressListState();
}
class _AddressListState extends State<AddressList> {
bool showHiddenAddresses = false;
void _toggleHiddenAddresses() {
setState(() {
showHiddenAddresses = !showHiddenAddresses;
});
updateItems();
}
List<ListItem> getItems(List<ListItem> list, bool showHidden) {
return list.where((element) {
if (element is WalletAddressListItem) {
if (showHidden && element.isHidden) return true;
if (!showHidden && !element.isHidden) return true;
return false;
}
return true;
}).toList();
}
List<ListItem> items = [];
void updateItems() {
setState(() {
items = getItems(widget.addressListViewModel.forceRecomputeItems, showHiddenAddresses);
});
}
@override
void initState() {
super.initState();
items = getItems(widget.addressListViewModel.items, showHiddenAddresses);
}
@override
Widget build(BuildContext context) {
bool editable = widget.onSelect == null;
return ListView.separated(
padding: EdgeInsets.all(0),
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
Widget cell = Container();
if (item is WalletAccountListHeader) {
cell = HeaderTile(
showTrailingButton: true,
walletAddressListViewModel: widget.addressListViewModel,
trailingButtonTap: () async {
if (widget.addressListViewModel.type == WalletType.monero ||
widget.addressListViewModel.type == WalletType.wownero ||
widget.addressListViewModel.type == WalletType.haven) {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>(),
);
updateItems();
} else {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<NanoAccountListPage>(),
);
updateItems();
}
},
title: S.of(context).accounts,
trailingIcon: Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
if (item is WalletAddressHiddenListHeader) {
cell = HeaderTile(
title: S.of(context).hidden_addresses,
walletAddressListViewModel: widget.addressListViewModel,
showTrailingButton: true,
showSearchButton: false,
trailingButtonTap: _toggleHiddenAddresses,
trailingIcon: Icon(
showHiddenAddresses ? Icons.toggle_on : Icons.toggle_off,
size: 20,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
if (item is WalletAddressListHeader) {
cell = HeaderTile(
title: S.of(context).addresses,
walletAddressListViewModel: widget.addressListViewModel,
showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
showSearchButton: true,
onSearchCallback: updateItems,
trailingButtonTap: () =>
Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
updateItems(); // refresh the new address
}),
trailingIcon: Icon(
Icons.add,
size: 20,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
if (item is WalletAddressListItem) {
if (item.isHidden && !showHiddenAddresses) {
cell = Container();
} else if (!item.isHidden && showHiddenAddresses) {
cell = Container();
} else {
cell = Observer(
builder: (_) {
final isCurrent =
item.address == widget.addressListViewModel.address.address && editable;
final backgroundColor = isCurrent
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surface;
final textColor = isCurrent
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface;
return AddressCell.fromItem(
item,
isCurrent: isCurrent,
walletType: widget.addressListViewModel.type,
hasBalance: widget.addressListViewModel.isBalanceAvailable,
hasReceived: widget.addressListViewModel.isReceivedAvailable,
// hasReceived:
backgroundColor: backgroundColor,
textColor: textColor,
onTap: (_) {
if (widget.onSelect != null) {
widget.onSelect!(item.address);
return;
}
widget.addressListViewModel.setAddress(item);
},
onEdit: editable
? () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item)
.then((value) {
updateItems(); // refresh the new address
})
: null,
isHidden: item.isHidden,
onHide: () => _hideAddress(item),
);
},
);
}
}
return index != 0
? cell
: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
child: cell,
);
},
);
}
void _hideAddress(WalletAddressListItem item) async {
await widget.addressListViewModel.toggleHideAddress(item);
updateItems();
}
}
@@ -1,157 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AnonpayCurrencyInputField extends StatelessWidget {
const AnonpayCurrencyInputField(
{super.key,
required this.onTapPicker,
required this.selectedCurrency,
required this.focusNode,
required this.controller,
required this.minAmount,
required this.maxAmount});
final Function() onTapPicker;
final Currency selectedCurrency;
final FocusNode focusNode;
final TextEditingController controller;
final String minAmount;
final String maxAmount;
@override
Widget build(BuildContext context) {
final arrowBottomPurple = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).colorScheme.primary,
height: 8,
);
return Column(
children: [
Container(
child: Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
children: [
Container(
padding: EdgeInsets.only(right: 8),
height: 32,
child: InkWell(
onTap: onTapPicker,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 5),
child: arrowBottomPurple,
),
Text(
selectedCurrency.name.toUpperCase(),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
),
),
if (selectedCurrency.tag != null)
Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Container(
height: 32,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.all(Radius.circular(6)),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
selectedCurrency.tag!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Text(
':',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: BaseTextFormField(
hasUnderlineBorder: true,
borderWidth: 0.0,
focusNode: focusNode,
controller: controller,
textInputAction: TextInputAction.next,
enabled: true,
textAlign: TextAlign.left,
keyboardType:
TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
hintText: '0.0000',
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
placeholderTextStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
validator: null,
),
),
],
),
),
],
),
),
),
Divider(height: 1, color: Theme.of(context).colorScheme.outlineVariant),
Container(
height: 15,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).min_value(minAmount, selectedCurrency.toString()),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
height: 1.2,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
SizedBox(width: 10),
Text(
S.of(context).max_value(maxAmount, selectedCurrency.toString()),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
height: 1.2,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
],
);
}
}
@@ -1,141 +0,0 @@
import 'package:cake_wallet/core/email_validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/anonpay_currency_input_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class AnonInvoiceForm extends StatelessWidget {
AnonInvoiceForm({
super.key,
required this.formKey,
required this.anonInvoicePageViewModel,
required this.isInvoice,
required this.amountController,
required this.nameController,
required this.emailController,
required this.descriptionController,
required this.depositAmountFocus,
}) : _nameFocusNode = FocusNode(),
_emailFocusNode = FocusNode(),
_descriptionFocusNode = FocusNode() {
amountController.text = anonInvoicePageViewModel.amount;
nameController.text = anonInvoicePageViewModel.receipientName;
descriptionController.text = anonInvoicePageViewModel.description;
emailController.text = anonInvoicePageViewModel.receipientEmail;
}
final TextEditingController amountController;
final TextEditingController nameController;
final TextEditingController emailController;
final TextEditingController descriptionController;
final AnonInvoicePageViewModel anonInvoicePageViewModel;
final FocusNode depositAmountFocus;
final FocusNode _nameFocusNode;
final FocusNode _emailFocusNode;
final FocusNode _descriptionFocusNode;
final GlobalKey<FormState> formKey;
final bool isInvoice;
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
isInvoice ? S.of(context).invoice_details : S.of(context).donation_link_details,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
if (isInvoice)
Observer(
builder: (_) {
return AnonpayCurrencyInputField(
onTapPicker: () => _presentPicker(context),
controller: amountController,
focusNode: depositAmountFocus,
maxAmount: anonInvoicePageViewModel.maximum?.toString() ?? '...',
minAmount: anonInvoicePageViewModel.minimum?.toString() ?? '...',
selectedCurrency: anonInvoicePageViewModel.selectedCurrency,
);
},
),
SizedBox(height: 24),
BaseTextFormField(
hasUnderlineBorder: true,
controller: nameController,
focusNode: _nameFocusNode,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).optional_name,
textInputAction: TextInputAction.next,
placeholderTextStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
validator: null,
),
SizedBox(height: 24),
BaseTextFormField(
hasUnderlineBorder: true,
controller: descriptionController,
focusNode: _descriptionFocusNode,
textInputAction: TextInputAction.next,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).optional_description,
placeholderTextStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
validator: null,
),
SizedBox(height: 24),
BaseTextFormField(
hasUnderlineBorder: true,
controller: emailController,
textInputAction: TextInputAction.next,
focusNode: _emailFocusNode,
suffixIcon: SizedBox(width: 36),
keyboardType: TextInputType.emailAddress,
hintText: S.of(context).optional_email_hint,
placeholderTextStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
validator: EmailValidator(),
),
SizedBox(height: 52),
],
),
);
}
void _presentPicker(BuildContext context) {
showPopUp<void>(
builder: (_) => CurrencyPicker(
selectedAtIndex: anonInvoicePageViewModel.selectedCurrencyIndex,
items: anonInvoicePageViewModel.currencies,
hintText: S.of(context).search_currency,
onItemSelected: anonInvoicePageViewModel.selectCurrency,
),
context: context,
);
}
}
@@ -1,86 +0,0 @@
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
import 'package:flutter/material.dart';
class AnonInvoiceStatusSection extends StatelessWidget {
const AnonInvoiceStatusSection({
super.key,
required this.invoiceInfo,
});
final AnonpayInvoiceInfo invoiceInfo;
@override
Widget build(BuildContext context) {
return Container(
width: 200,
padding: EdgeInsets.all(19),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(30),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.current.status,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SyncIndicatorIcon(
boolMode: false,
value: invoiceInfo.status,
size: 6,
),
SizedBox(width: 5),
Text(
invoiceInfo.status,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
],
),
),
],
),
SizedBox(height: 27),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ID',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Text(
invoiceInfo.invoiceId,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
],
),
);
}
}
@@ -1,54 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CopyLinkItem extends StatelessWidget {
const CopyLinkItem({super.key, required this.url, required this.title});
final String url;
final String title;
@override
Widget build(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_address.png',
color: Theme.of(context).colorScheme.onSurface);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
),
SizedBox(width: 50),
Row(
children: [
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: url));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: copyImage,
),
SizedBox(width: 20),
IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => ShareUtil.share(text: url, context: context),
icon: Icon(
Icons.share,
color: Theme.of(context).colorScheme.primary,
),
)
],
)
],
);
}
}
@@ -1,108 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter/material.dart';
class HeaderTile extends StatefulWidget {
HeaderTile({
required this.title,
required this.walletAddressListViewModel,
this.showSearchButton = false,
this.showTrailingButton = false,
this.trailingButtonTap,
this.onSearchCallback,
this.trailingIcon,
});
final String title;
final WalletAddressListViewModel walletAddressListViewModel;
final bool showSearchButton;
final bool showTrailingButton;
final VoidCallback? trailingButtonTap;
final VoidCallback? onSearchCallback;
final Icon? trailingIcon;
@override
_HeaderTileState createState() => _HeaderTileState();
}
class _HeaderTileState extends State<HeaderTile> {
bool _isSearchActive = false;
@override
Widget build(BuildContext context) {
final searchIcon = Icon(Icons.search, color: Theme.of(context).colorScheme.primary);
return Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
color: Theme.of(context).colorScheme.surface,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_isSearchActive
? Expanded(
child: BaseTextFormField(
onChanged: (value) {
widget.walletAddressListViewModel.updateSearchText(value);
widget.onSearchCallback?.call();
},
cursorColor: Theme.of(context).colorScheme.onSurface,
cursorWidth: 0.5,
isDense: true,
hintText: '${S.of(context).search}...',
contentPadding: EdgeInsets.all(4),
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
),
autofocus: true,
),
)
: Text(
widget.title,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
Row(
children: [
if (widget.showSearchButton)
GestureDetector(
onTap: () {
setState(() {
_isSearchActive = !_isSearchActive;
widget.walletAddressListViewModel.updateSearchText('');
});
},
child: Container(
height: 32,
width: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.secondaryContainer,
),
child: searchIcon,
),
),
const SizedBox(width: 8),
if (widget.showTrailingButton)
GestureDetector(
onTap: widget.trailingButtonTap,
child: Container(
height: 32,
width: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.secondaryContainer,
),
child: widget.trailingIcon,
),
),
],
),
],
),
);
}
}
@@ -1,321 +0,0 @@
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/brightness_util.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/payment_uris.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart';
class QRWidget extends StatelessWidget {
QRWidget({
required this.addressListViewModel,
this.qrVersion,
this.heroTag,
required this.amountController,
required this.formKey,
this.amountTextFieldFocusNode,
});
final WalletAddressListViewModel addressListViewModel;
final TextEditingController amountController;
final FocusNode? amountTextFieldFocusNode;
final GlobalKey<FormState> formKey;
final int? qrVersion;
final String? heroTag;
PaymentURI get addressUri {
return addressListViewModel.uri;
}
@override
Widget build(BuildContext context) {
final copyImage = Image.asset(
'assets/images/copy_address.png',
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
// This magic number for wider screen sets the text input focus at center of the inputfield
final _width =
responsiveLayoutUtil.shouldRenderMobileUI ? MediaQuery.of(context).size.width : 500;
return Center(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
children: [
Row(
children: <Widget>[
Spacer(flex: 3),
Observer(
builder: (_) => Flexible(
flex: 9,
child: GestureDetector(
onTap: () {
BrightnessUtil.changeBrightnessForFunction(
() async {
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: QrViewData(
embeddedImagePath: addressListViewModel.qrImage,
data: addressUri.toString(),
heroTag: heroTag,
),
);
},
);
},
child: Hero(
tag: Key(heroTag ?? addressUri.toString()),
child: Center(
child: Container(
padding: EdgeInsets.zero,
decoration: BoxDecoration(
border: Border(top: BorderSide.none),
borderRadius: BorderRadius.all(Radius.circular(5)),
color: Theme.of(context).colorScheme.surface,
),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12.5),
child: AspectRatio(
aspectRatio: 1.0,
child: QrImage(
embeddedImagePath: addressListViewModel.qrImage,
data: addressUri.toString(),
size: 230,
),
),
),
if (addressListViewModel.isPayjoinUnavailable &&
!addressListViewModel.isSilentPayments &&
!addressListViewModel.isLightning &&
!addressListViewModel.isBitcoinViewOnly) ...[
GestureDetector(
onTap: () => _onPayjoinInactivePressed(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(
top: 4,
bottom: 4,
right: 4,
),
child: Image.asset(
'assets/images/payjoin.png',
width: 20,
),
),
Text(
S.of(context).payjoin_unavailable,
style:
Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
Padding(
padding: EdgeInsets.only(
top: 4,
bottom: 4,
left: 4,
),
child: CircleAvatar(
radius: 7,
backgroundColor: Colors.black,
child: Icon(
Icons.question_mark,
size: 10,
color: Colors.white,
),
),
),
],
),
),
],
if (addressListViewModel.isPayjoinAvailable) ...[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(
top: 4,
bottom: 4,
right: 4,
),
child: Image.asset(
'assets/images/payjoin.png',
width: 20,
),
),
Text(
S.of(context).payjoin_enabled,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
]
],
),
),
),
),
),
),
),
Spacer(flex: 3)
],
),
],
),
Observer(
builder: (_) => Row(
children: <Widget>[
Expanded(
child: Form(
key: formKey,
child: CurrencyAmountTextField(
hasUnderlineBorder: true,
borderWidth: 0.0,
selectedCurrency: _currencyName,
selectedCurrencyDecimals:
addressListViewModel.selectedCurrencyDecimals,
amountFocusNode: amountTextFieldFocusNode,
amountController: amountController,
padding: EdgeInsets.only(top: 20, left: _width / 4),
isAmountEditable: true,
tag: addressListViewModel.selectedCurrency.tag,
onTapPicker: () => _presentPicker(context),
isPickerEnable: true,
),
),
),
],
)),
Divider(height: 1, color: Theme.of(context).colorScheme.outlineVariant),
Padding(
padding: EdgeInsets.only(top: 12, bottom: 8),
child: Builder(
builder: (context) => Observer(
builder: (context) => GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: addressUri.address));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: AddressFormatter.buildSegmentedAddress(
address: addressUri.address,
walletType: addressListViewModel.type,
textAlign: TextAlign.center,
evenTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
Padding(
padding: EdgeInsets.only(left: 12),
child: copyImage,
)
],
),
),
),
),
),
Observer(
builder: (_) => Offstage(
offstage: !addressListViewModel.isPayjoinAvailable,
child: Padding(
padding: EdgeInsets.only(top: 12),
child: PrimaryImageButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: addressUri.toString()));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
image: Image.asset(
'assets/images/payjoin.png',
width: 25,
),
text: S.of(context).copy_payjoin_address,
color: Theme.of(context).colorScheme.surfaceContainer,
textColor: Theme.of(context).colorScheme.onSurface,
),
),
),
),
],
),
),
);
}
String get _currencyName {
if (addressListViewModel.selectedCurrency is CryptoCurrency) {
if (addressListViewModel.useSatoshi) return "SATS";
return (addressListViewModel.selectedCurrency as CryptoCurrency).title.toUpperCase();
}
return addressListViewModel.selectedCurrency.name.toUpperCase();
}
void _presentPicker(BuildContext context) async {
await showPopUp<void>(
builder: (_) => CurrencyPicker(
selectedAtIndex: addressListViewModel.selectedCurrencyIndex,
items: addressListViewModel.currencies,
hintText: S.of(context).search_currency,
onItemSelected: addressListViewModel.selectCurrency,
),
context: context,
);
// update amount if currency changed
addressListViewModel.changeAmount(amountController.text);
}
void _onPayjoinInactivePressed(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => InfoBottomSheet(
titleText: S.of(context).payjoin_unavailable_sheet_title,
content: S.of(context).payjoin_unavailable_sheet_content,
footerType: FooterType.doubleActionButton,
doubleActionLeftButtonText: S.of(context).learn_more,
onLeftActionButtonPressed: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/bitcoin/#payjoin"),
mode: LaunchMode.externalApplication),
doubleActionRightButtonText: S.of(context).ok,
onRightActionButtonPressed: () => Navigator.of(context).pop(),
),
);
}
}
@@ -1,139 +0,0 @@
import 'dart:convert';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ReleaseNotesScreen extends StatelessWidget {
const ReleaseNotesScreen({
required this.title,
});
final String title;
Future<List<String>> _loadStrings() async {
String notesContent = await rootBundle.loadString(
isMoneroOnly ? 'assets/text/Monerocom_Release_Notes.txt' : 'assets/text/Release_Notes.txt');
return LineSplitter().convert(notesContent);
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
AlertBackground(
child: AlertDialog(
insetPadding: EdgeInsets.only(left: 16, right: 16, bottom: 48),
elevation: 0.0,
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30))),
content: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
gradient: LinearGradient(colors: [
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surfaceContainer,
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Stack(
children: [
SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Container(
alignment: Alignment.bottomCenter,
child: DefaultTextStyle(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
decoration: TextDecoration.none,
fontSize: 24.0,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
child: Text(title),
),
),
),
),
SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(top: 48, bottom: 16),
child: Container(
width: double.maxFinite,
child: Column(
children: <Widget>[
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.7,
),
child: _getNotesWidget(),
)
],
),
),
),
),
],
),
),
),
),
),
AlertCloseButton(
bottom: 30,
)
],
);
}
Widget _getNotesWidget() {
return FutureBuilder<List<String>>(
future: _loadStrings(),
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return _getNoteItemWidget(snapshot.data![index], context);
},
);
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Center(child: CircularProgressIndicator());
}
},
);
}
Widget _getNoteItemWidget(String myString, BuildContext context) {
return Column(
children: [
DefaultTextStyle(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
decoration: TextDecoration.none,
fontSize: 16.0,
color: Theme.of(context).colorScheme.onSurface,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text('•'),
),
Expanded(
child: Text(myString),
),
],
)),
SizedBox(
height: 16.0,
)
],
);
}
}
@@ -1,116 +0,0 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/scheduler.dart';
class SweepingWalletPage extends BasePage {
SweepingWalletPage();
static const aspectRatioImage = 1.25;
final welcomeImageLight = Image.asset('assets/images/welcome_light.png');
final welcomeImageDark = Image.asset('assets/images/welcome.png');
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
resizeToAvoidBottomInset: false,
body: body(context));
}
@override
Widget body(BuildContext context) {
final welcomeImage = currentTheme.isDark ? welcomeImageDark : welcomeImageLight;
return SweepingWalletWidget(
aspectRatioImage: aspectRatioImage,
welcomeImage: welcomeImage,
);
}
}
class SweepingWalletWidget extends StatefulWidget {
const SweepingWalletWidget({
required this.aspectRatioImage,
required this.welcomeImage,
});
final double aspectRatioImage;
final Image welcomeImage;
@override
State<SweepingWalletWidget> createState() => _SweepingWalletWidgetState();
}
class _SweepingWalletWidgetState extends State<SweepingWalletWidget> {
@override
void initState() {
SchedulerBinding.instance.addPostFrameCallback((_) async {});
super.initState();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Container(
padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
flex: 2,
child: AspectRatio(
aspectRatio: widget.aspectRatioImage,
child: FittedBox(child: widget.welcomeImage, fit: BoxFit.fill))),
Flexible(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 24),
child: Text(
S.of(context).please_wait,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
S.of(context).sweeping_wallet,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
S.of(context).sweeping_wallet_alert,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
),
],
),
],
))
],
)));
}
}
-845
View File
@@ -1,845 +0,0 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
import 'package:cake_wallet/src/widgets/adaptable_page_view.dart';
import 'package:cake_wallet/src/widgets/add_template_button.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/simple_checkbox.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:url_launcher/url_launcher.dart';
class SendPage extends BasePage {
SendPage({
required this.sendViewModel,
required this.authService,
required this.paymentViewModel,
required this.walletSwitcherViewModel,
this.initialPaymentRequest,
}) : _formKey = GlobalKey<FormState>();
final SendViewModel sendViewModel;
final PaymentViewModel paymentViewModel;
final WalletSwitcherViewModel walletSwitcherViewModel;
final AuthService authService;
final GlobalKey<FormState> _formKey;
final controller = PageController(initialPage: 0);
final PaymentRequest? initialPaymentRequest;
final FocusNode _cryptoAmountFocus = FocusNode();
final FocusNode _fiatAmountFocus = FocusNode();
final currentPage = ValueNotifier<int>(0);
bool _effectsInstalled = false;
ContactRecord? newContactAddress;
@override
String get title => S.current.send;
@override
bool get gradientAll => true;
@override
bool get resizeToAvoidBottomInset => false;
@override
bool get extendBodyBehindAppBar => true;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override
Widget? leading(BuildContext context) {
final _backButton = Icon(
Icons.arrow_back_ios,
color: Theme.of(context).colorScheme.primary,
size: 16,
);
final _closeButton = currentTheme.isDark ? closeButtonImageDarkTheme : closeButtonImage;
final isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
return MergeSemantics(
child: SizedBox(
height: isMobileView ? 37 : 45,
width: isMobileView ? 47 : 45,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
child: TextButton(
style: ButtonStyle(
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent),
),
onPressed: () => onClose(context),
child: !isMobileView ? _closeButton : _backButton,
),
),
),
),
);
}
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
void onClose(BuildContext context) {
sendViewModel.onClose();
Navigator.of(context).pop();
}
@override
Widget? middle(BuildContext context) {
final supMiddle = super.middle(context);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Observer(
builder: (_) => SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend),
),
),
if (supMiddle != null) supMiddle
],
);
}
@override
Widget trailing(context) => Observer(
builder: (_) => sendViewModel.isBatchSending
? TrailButton(
caption: S.of(context).remove,
onPressed: () {
var pageToJump = (controller.page?.round() ?? 0) - 1;
pageToJump = pageToJump > 0 ? pageToJump : 0;
final output = _defineCurrentOutput();
sendViewModel.removeOutput(output);
controller.jumpToPage(pageToJump);
},
)
: TrailButton(
caption: S.of(context).clear,
onPressed: () {
final output = _defineCurrentOutput();
_formKey.currentState?.reset();
output.reset();
},
),
);
@override
Widget body(BuildContext context) {
_setEffects(context);
return ValueListenableBuilder(
valueListenable: currentPage,
builder: (context, value, child) {
return Observer(builder: (_) {
List<Widget> sendCards = [];
List<KeyboardActionsItem> keyboardActions = [];
for (final output in sendViewModel.outputs) {
final isCurrent = sendViewModel.outputs.indexOf(output) == value;
sendCards.add(
SendCard(
currentTheme: currentTheme,
key: output.key,
output: output,
sendViewModel: sendViewModel,
paymentViewModel: paymentViewModel,
walletSwitcherViewModel: walletSwitcherViewModel,
initialPaymentRequest: initialPaymentRequest,
cryptoAmountFocus: isCurrent ? _cryptoAmountFocus : null,
fiatAmountFocus: isCurrent ? _fiatAmountFocus : null,
),
);
keyboardActions.add(
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
);
keyboardActions.add(
KeyboardActionsItem(
focusNode: _fiatAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
);
}
return Stack(
children: [
KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
keyboardBarColor: Theme.of(context).colorScheme.surface,
nextFocus: false,
actions: keyboardActions,
),
child: Container(
height: 0,
color: Colors.transparent,
),
),
GestureDetector(
onLongPress: () => sendViewModel.balanceViewModel.isReversing =
!sendViewModel.balanceViewModel.isReversing,
onLongPressUp: () => sendViewModel.balanceViewModel.isReversing =
!sendViewModel.balanceViewModel.isReversing,
child: RepaintBoundary(
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: <Widget>[
PageViewHeightAdaptable(
controller: controller,
children: sendCards,
),
SizedBox(height: 10),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
child: Container(
height: 10,
child: Observer(
builder: (_) {
final count = sendViewModel.outputs.length;
return count > 1
? Semantics(
label: 'Page Indicator',
hint: 'Swipe to change receiver',
excludeSemantics: true,
child: SmoothPageIndicator(
controller: controller,
count: count,
effect: ScrollingDotsEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context)
.colorScheme
.primary
.withAlpha(100),
activeDotColor:
Theme.of(context).colorScheme.primary,
),
),
)
: Offstage();
},
),
),
),
Container(
height: 40,
width: double.infinity,
padding: EdgeInsets.only(left: 24),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Observer(
builder: (_) {
final templates = sendViewModel.templates;
final itemCount = templates.length;
return Row(
children: <Widget>[
AddTemplateButton(
key: ValueKey('send_page_add_template_button_key'),
onTap: () => Navigator.of(context)
.pushNamed(Routes.sendTemplate),
currentTemplatesLength: templates.length,
),
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
return TemplateTile(
key: UniqueKey(),
to: template.name,
hasMultipleRecipients:
template.additionalRecipients != null &&
template.additionalRecipients!.length > 1,
amount: template.isCurrencySelected
? template.amount
: template.amountFiat,
from: template.isCurrencySelected
? template.cryptoCurrency
: template.fiatCurrency,
onTap: () async {
sendViewModel.state =
LoadingTemplateExecutingState();
if (template.additionalRecipients?.isNotEmpty ??
false) {
sendViewModel.clearOutputs();
for (int i = 0;
i < template.additionalRecipients!.length;
i++) {
Output output;
try {
output = sendViewModel.outputs[i];
} catch (e) {
sendViewModel.addOutput();
output = sendViewModel.outputs[i];
}
await _setInputsFromTemplate(
context,
output: output,
template:
template.additionalRecipients![i],
);
}
} else {
final output = _defineCurrentOutput();
await _setInputsFromTemplate(
context,
output: output,
template: template,
);
}
sendViewModel.state = InitialExecutionState();
},
onRemove: () {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).template,
alertContent: S
.of(context)
.confirm_delete_template,
rightButtonText: S.of(context).delete,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
Navigator.of(dialogContext).pop();
sendViewModel.sendTemplateViewModel
.removeTemplate(
template: template);
},
actionLeftButton: () =>
Navigator.of(dialogContext).pop());
},
);
},
);
},
),
],
);
},
),
),
),
],
),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(
children: [
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
key: ValueKey('send_page_add_receiver_button_key'),
onPressed: () {
sendViewModel.addOutput();
Future.delayed(const Duration(milliseconds: 250), () {
controller.jumpToPage(sendViewModel.outputs.length - 1);
});
},
text: S.of(context).add_receiver,
color: Colors.transparent,
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
isDottedBorder: true,
borderColor: Theme.of(context).colorScheme.outline,
)),
Observer(
builder: (_) {
return LoadingPrimaryButton(
key: ValueKey('send_page_send_button_key'),
onPressed: () async {
//Request dummy node to get the focus out of the text fields
FocusScope.of(context).requestFocus(FocusNode());
if (sendViewModel.state is IsExecutingState) return;
if (_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
if (sendViewModel.outputs.length > 1) {
showErrorValidationAlert(context);
}
return;
}
final hasInvalidItems = sendViewModel.outputs.any((item) =>
item.address.isEmpty ||
(item.cryptoAmount.isEmpty && !item.sendAll));
if (hasInvalidItems) {
showErrorValidationAlert(context);
return;
}
if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.hardwareWalletViewModel!
.isConnected(sendViewModel.walletType)) {
await Navigator.of(context)
.pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
hardwareWalletType: sendViewModel
.wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (BuildContext context, _) {
sendViewModel.hardwareWalletViewModel!
.initWallet(sendViewModel.wallet);
Navigator.of(context).pop();
},
));
} else {
sendViewModel.hardwareWalletViewModel!
.initWallet(sendViewModel.wallet);
}
}
if (sendViewModel.wallet.type == WalletType.monero) {
var amount = Money.zero(sendViewModel.wallet.currency);
for (var item in sendViewModel.outputs) {
amount += item.cryptoAmountMoney;
}
if (monero!
.needExportOutputs(sendViewModel.wallet, amount)) {
await Navigator.of(context).pushNamed(
Routes.urqrAnimatedPage,
arguments:
monero!.exportOutputsUR(sendViewModel.wallet));
await Future.delayed(Duration(
seconds: 1)); // wait for monero to refresh the state
}
if (monero!
.needExportOutputs(sendViewModel.wallet, amount)) {
return;
}
}
final check = sendViewModel.shouldDisplayTotp();
authService.authenticateAction(
context,
conditionToDetermineIfToUse2FA: check,
onAuthSuccess: (value) async {
if (value) {
await sendViewModel.createTransaction();
}
},
);
},
text: _sendButtonText(context),
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: sendViewModel.state is IsExecutingState ||
sendViewModel.state is TransactionCommitting ||
sendViewModel.state is IsAwaitingDeviceResponseState ||
sendViewModel.state is LoadingTemplateExecutingState,
isDisabled: !sendViewModel.isReadyForSend ||
sendViewModel.state is ExecutedSuccessfullyState,
);
},
)
],
)),
),
),
),
],
);
});
});
}
BuildContext? dialogContext;
BuildContext? loadingBottomSheetContext;
void _setEffects(BuildContext context) {
if (_effectsInstalled) return;
if (sendViewModel.isElectrumWallet) {
bitcoin!.updateFeeRates(sendViewModel.wallet);
}
controller.addListener(() {
if (!controller.hasClients) return;
currentPage.value = controller.page!.round();
});
reaction((_) => sendViewModel.state, (ExecutionState state) async {
if (dialogContext != null && dialogContext?.mounted == true) {
Navigator.of(dialogContext!).pop();
}
if (state is! IsExecutingState &&
loadingBottomSheetContext != null &&
loadingBottomSheetContext!.mounted) {
Navigator.of(loadingBottomSheetContext!).pop();
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
key: ValueKey('send_page_send_failure_dialog_key'),
buttonKey: ValueKey('send_page_send_failure_dialog_button_key'),
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
),
);
},
);
}
if (state is IsExecutingState) {
// wait a bit to avoid showing the loading dialog if transaction is failed
await Future.delayed(const Duration(milliseconds: 300));
final currentState = sendViewModel.state;
if (currentState is ExecutedSuccessfullyState || currentState is FailureState) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (context) {
loadingBottomSheetContext = context;
return LoadingBottomSheet(
titleText: S.of(context).generating_transaction,
);
},
);
}
});
}
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (context.mounted) {
final result = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) {
return Observer(
builder: (_) => ConfirmSendingBottomSheet(
key: ValueKey('send_page_confirm_sending_bottom_sheet_key'),
titleText: S.of(bottomSheetContext).confirm_transaction,
accessibleNavigationModeSlideActionButtonText: S.of(bottomSheetContext).send,
footerType: FooterType.slideActionButton,
isSlideActionEnabled: sendViewModel.isReadyForSend,
walletType: sendViewModel.walletType,
titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
currency: sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue: sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
sendViewModel.pendingTransaction!.amountFormatted,
sendViewModel.selectedCryptoCurrency),
fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted,
fee: isEVMCompatibleChain(sendViewModel.walletType)
? S.of(bottomSheetContext).send_estimated_fee
: S.of(bottomSheetContext).send_fee,
feeValue:
"${sendViewModel.amountParsingProxy.getDisplayCryptoAmount(sendViewModel.pendingTransaction!.feeFormattedValue, sendViewModel.selectedCryptoCurrency)} ${sendViewModel.amountParsingProxy.getCryptoSymbol(sendViewModel.wallet.currency)}",
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: sendViewModel.outputs,
onSlideActionComplete: () async {
Navigator.of(bottomSheetContext).pop(true);
sendViewModel.commitTransaction(context);
},
change: sendViewModel.pendingTransaction!.change,
isOpenCryptoPay: sendViewModel.ocpRequest != null,
amountParsingProxy: sendViewModel.amountParsingProxy,
),
);
},
);
if (result == null) sendViewModel.dismissTransaction();
}
});
}
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!context.mounted) return;
newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
if (newContactAddress?.address != null &&
isRegularElectrumAddress(newContactAddress!.address)) {
newContactAddress = null;
}
bool showContactSheet = (newContactAddress != null && sendViewModel.showAddressBookPopup);
await showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext bottomSheetContext) {
return showContactSheet && sendViewModel.ocpRequest == null
? InfoBottomSheet(
footerType: FooterType.doubleActionButton,
titleText: S.of(bottomSheetContext).transaction_sent,
contentImage: 'assets/images/contact.png',
contentImageColor: Theme.of(context).colorScheme.onSurface,
content: S.of(bottomSheetContext).add_contact_to_address_book,
leftActionButtonKey:
ValueKey('send_page_add_contact_bottom_sheet_no_button_key'),
rightActionButtonKey:
ValueKey('send_page_add_contact_bottom_sheet_yes_button_key'),
bottomActionPanel: Padding(
padding: const EdgeInsets.only(left: 34.0),
child: Row(
children: [
SimpleCheckbox(
onChanged: (value) =>
sendViewModel.setShowAddressBookPopup(!value)),
const SizedBox(width: 8),
Text(
'Don’t ask me next time',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.titleLarge!.color,
decoration: TextDecoration.none,
),
),
],
),
),
doubleActionLeftButtonText: 'No',
doubleActionRightButtonText: 'Yes',
onLeftActionButtonPressed: () {
Navigator.of(bottomSheetContext).pop();
if (context.mounted) {
Navigator.of(context)
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
}
RequestReviewHandler.requestReview();
newContactAddress = null;
},
onRightActionButtonPressed: () {
Navigator.of(bottomSheetContext).pop();
RequestReviewHandler.requestReview();
if (context.mounted) {
Navigator.of(context).pushNamed(Routes.addressBookAddContact,
arguments: newContactAddress);
}
newContactAddress = null;
},
)
: InfoBottomSheet(
footerType: FooterType.singleActionButton,
titleText: S.of(bottomSheetContext).transaction_sent,
contentImage: 'assets/images/birthday_cake.png',
singleActionButtonText: S.of(bottomSheetContext).close,
singleActionButtonKey: ValueKey('send_page_transaction_sent_button_key'),
onSingleActionButtonPressed: () {
Navigator.of(bottomSheetContext).pop();
Future.delayed(Duration.zero, () {
if (context.mounted) {
Navigator.of(context)
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
}
RequestReviewHandler.requestReview();
newContactAddress = null;
});
},
);
},
);
if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) {
// wait a second so it's not as jarring:
await Future.delayed(Duration(seconds: 1));
try {
launchUrl(
Uri.parse(initialPaymentRequest!.callbackUrl!),
mode: LaunchMode.externalApplication,
);
} catch (e) {
printV(e);
}
}
sendViewModel.clearOutputs();
});
}
if (state is IsDeviceSigningResponseState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!context.mounted) return;
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (context) {
dialogContext = context;
return LoadingBottomSheet(titleText: S.of(context).processing_signed_tx);
},
);
});
}
if (state is IsAwaitingDeviceResponseState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!context.mounted) return;
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (context) {
dialogContext = context;
return InfoBottomSheet(
footerType: FooterType.singleActionButton,
titleText: S.of(context).proceed_on_device,
contentImage: 'assets/images/hardware_wallet/ledger_nano_x.png',
contentImageColor: Theme.of(context).colorScheme.onSurface,
content: S.of(context).proceed_on_device_description,
singleActionButtonText: S.of(context).cancel,
onSingleActionButtonPressed: () {
sendViewModel.state = InitialExecutionState();
Navigator.of(context).pop();
},
);
});
});
}
});
_effectsInstalled = true;
}
Future<void> _setInputsFromTemplate(BuildContext context,
{required Output output, required Template template}) async {
output.address = template.address;
if (template.isCurrencySelected) {
sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency);
output.setCryptoAmount(template.amount);
} else {
final fiatFromTemplate =
FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency);
sendViewModel.setFiatCurrency(fiatFromTemplate);
output.setFiatAmount(template.amountFiat);
}
output.resetParsedAddress();
}
Output _defineCurrentOutput() {
if (controller.page == null) throw Exception('Controller page is null');
final itemCount = controller.page!.round();
return sendViewModel.outputs[itemCount];
}
void showErrorValidationAlert(BuildContext context) => showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: 'Please, check receiver forms',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
),
);
bool isRegularElectrumAddress(String address) {
final supportedTypes = [CryptoCurrency.btc, CryptoCurrency.ltc, CryptoCurrency.bch];
final excludedPatterns = [
RegExp(AddressValidator.silentPaymentAddressPatternMainnet),
RegExp(AddressValidator.silentPaymentAddressPatternTestnet),
RegExp(AddressValidator.mWebAddressPattern),
RegExp(AddressValidator.bolt11InvoiceMatcher),
];
final trimmed = address.trim();
bool isValid = false;
for (final type in supportedTypes) {
final addressPattern = AddressValidator.getAddressFromStringPattern(type);
if (addressPattern != null) {
final regex = RegExp('^$addressPattern\$');
if (regex.hasMatch(trimmed)) {
isValid = true;
break;
}
}
}
for (final pattern in excludedPatterns) {
if (pattern.hasMatch(trimmed)) return false;
}
return isValid;
}
String _sendButtonText(BuildContext context) {
if (!sendViewModel.isReadyForSend) return S.of(context).synchronizing;
if (sendViewModel.payjoinUri != null) return S.of(context).send_payjoin;
return S.of(context).send;
}
}
@@ -1,189 +0,0 @@
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/src/screens/send/widgets/send_template_card.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class SendTemplatePage extends BasePage {
SendTemplatePage({required this.sendTemplateViewModel});
final SendTemplateViewModel sendTemplateViewModel;
final _formKey = GlobalKey<FormState>();
final controller = PageController(initialPage: 0);
@override
String get title => S.current.exchange_new_template;
@override
bool get extendBodyBehindAppBar => true;
@override
bool get gradientAll => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override
Widget trailing(context) => Observer(
builder: (_) {
return sendTemplateViewModel.recipients.length > 1
? TrailButton(
caption: S.of(context).remove,
onPressed: () {
int pageToJump = (controller.page?.round() ?? 0) - 1;
pageToJump = pageToJump > 0 ? pageToJump : 0;
final recipient = _defineCurrentRecipient();
sendTemplateViewModel.removeRecipient(recipient);
controller.jumpToPage(pageToJump);
},
)
: TrailButton(
caption: S.of(context).clear,
onPressed: () {
final recipient = _defineCurrentRecipient();
_formKey.currentState?.reset();
recipient.reset();
},
);
},
);
@override
Widget body(BuildContext context) {
return Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: [
Container(
height: 460,
child: Observer(
builder: (_) {
return PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: sendTemplateViewModel.recipients.length,
itemBuilder: (_, index) {
final template = sendTemplateViewModel.recipients[index];
return SendTemplateCard(
template: template,
index: index,
sendTemplateViewModel: sendTemplateViewModel,
);
},
);
},
),
),
Padding(
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
child: Container(
height: 10,
child: Observer(
builder: (_) {
final count = sendTemplateViewModel.recipients.length;
return count > 1
? Semantics(
button: false,
label: 'Page Indicator',
hint: 'Swipe to change receiver',
excludeSemantics: true,
child: SmoothPageIndicator(
controller: controller,
count: count,
effect: ScrollingDotsEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).colorScheme.outlineVariant,
activeDotColor: Theme.of(context).colorScheme.primary,
),
),
)
: Offstage();
},
),
),
),
],
),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(
children: [
if (sendTemplateViewModel.hasMultiRecipient)
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () {
sendTemplateViewModel.addRecipient();
Future.delayed(const Duration(milliseconds: 250), () {
controller.jumpToPage(sendTemplateViewModel.recipients.length - 1);
});
},
text: S.of(context).add_receiver,
color: Colors.transparent,
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
isDottedBorder: true,
borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
),
),
PrimaryButton(
onPressed: () {
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
final mainTemplate = sendTemplateViewModel.recipients[0];
final additionalRecipients = sendTemplateViewModel.recipients
.map((element) => element.toTemplate(
cryptoCurrency: element.selectedCurrency.title,
fiatCurrency: sendTemplateViewModel.fiatCurrency))
.toList();
sendTemplateViewModel.addTemplate(
isCurrencySelected: mainTemplate.isCryptoSelected,
name: mainTemplate.name,
address: mainTemplate.address,
cryptoCurrency: mainTemplate.selectedCurrency.title,
amount: mainTemplate.output.cryptoAmount,
amountFiat: mainTemplate.output.fiatAmount,
additionalRecipients: additionalRecipients,
);
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
)
],
),
),
);
}
TemplateViewModel _defineCurrentRecipient() {
if (controller.page == null) {
throw Exception('Controller page is null');
}
final itemCount = controller.page!.round();
return sendTemplateViewModel.recipients[itemCount];
}
}
@@ -1,33 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/Info_page.dart';
import 'package:flutter/cupertino.dart';
class TransactionSuccessPage extends InfoPage {
TransactionSuccessPage({required this.content})
: super(
imageLightPath: 'assets/images/birthday_cake.png',
imageDarkPath: 'assets/images/birthday_cake.png',
);
final String content;
@override
bool get onWillPop => false;
@override
String get pageTitle => 'Transaction Sent Successfully';
@override
String get pageDescription => content;
@override
String get buttonText => S.current.ok;
@override
Key? get buttonKey => ValueKey('transaction_success_info_page_button_key');
@override
void Function(BuildContext) get onPressed => (BuildContext context) {
if (context.mounted) Navigator.of(context).pop();
};
}
@@ -1,116 +0,0 @@
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
class ChooseYatAddressAlert extends BaseAlertDialog {
ChooseYatAddressAlert({
required this.alertTitle,
required this.alertContent,
required this.addresses,
});
final String alertTitle;
final String alertContent;
final List<String> addresses;
@override
String get titleText => alertTitle;
@override
String get contentText => alertContent;
@override
bool get barrierDismissible => false;
@override
Widget actionButtons(BuildContext context) => ChooseYatAddressButtons(addresses);
}
class ChooseYatAddressButtons extends StatefulWidget {
ChooseYatAddressButtons(this.addresses);
final List<String> addresses;
@override
ChooseYatAddressButtonsState createState() => ChooseYatAddressButtonsState(addresses);
}
class ChooseYatAddressButtonsState extends State<ChooseYatAddressButtons> {
ChooseYatAddressButtonsState(this.addresses) : itemCount = addresses?.length ?? 0;
final List<String> addresses;
final int itemCount;
final double backgroundHeight = 118;
final double thumbHeight = 72;
ScrollController controller = ScrollController();
double fromTop = 0;
@override
Widget build(BuildContext context) {
controller.addListener(() {
fromTop = controller.hasClients
? (controller.offset /
controller.position.maxScrollExtent *
(backgroundHeight - thumbHeight))
: 0;
setState(() {});
});
return Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Container(
width: 300,
height: 158,
color: Theme.of(context).colorScheme.surface,
child: ListView.separated(
controller: controller,
padding: EdgeInsets.all(0),
itemCount: itemCount,
separatorBuilder: (_, __) => const HorizontalSectionDivider(),
itemBuilder: (context, index) {
final address = addresses[index];
return GestureDetector(
onTap: () => Navigator.of(context).pop<String>(address),
child: Container(
width: 300,
height: 52,
padding: EdgeInsets.only(left: 24, right: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
address,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
decoration: TextDecoration.none,
),
),
)
],
),
),
);
},
),
),
if (itemCount > 3)
CakeScrollbar(
backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight,
fromTop: fromTop,
)
],
);
}
}
File diff suppressed because it is too large Load Diff
@@ -1,256 +0,0 @@
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:mobx/mobx.dart';
class SendTemplateCard extends StatelessWidget {
SendTemplateCard({
super.key,
required this.template,
required this.index,
required this.sendTemplateViewModel,
});
final TemplateViewModel template;
final int index;
final SendTemplateViewModel sendTemplateViewModel;
final _addressController = TextEditingController();
final _cryptoAmountController = TextEditingController();
final _fiatAmountController = TextEditingController();
final _nameController = TextEditingController();
final FocusNode _cryptoAmountFocus = FocusNode();
final FocusNode _fiatAmountFocus = FocusNode();
bool _effectsInstalled = false;
@override
Widget build(BuildContext context) {
_setEffects(context);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
child: Column(
children: <Widget>[
SizedBox(height: 32),
if (index == 0)
BaseTextFormField(
contentPadding: EdgeInsets.symmetric(vertical: 8),
hasUnderlineBorder: true,
controller: _nameController,
hintText: sendTemplateViewModel.recipients.length > 1
? S.of(context).template_name
: S.of(context).send_name,
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
),
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
validator: sendTemplateViewModel.templateValidator,
fillColor: Theme.of(context).colorScheme.surfaceContainerHighest,
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Observer(
builder: (context) {
return AddressTextField(
hasUnderlineBorder: true,
contentPadding: EdgeInsets.symmetric(vertical: 8),
selectedCurrency: template.selectedCurrency,
controller: _addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
_addressController.text = paymentRequest.address;
_cryptoAmountController.text = paymentRequest.amount;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
onPushPasteButton: (context) async {
template.output.resetParsedAddress();
},
onPushAddressBookButton: (context) async {
template.output.resetParsedAddress();
},
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
),
hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
validator: sendTemplateViewModel.addressValidator,
fillColor: Theme.of(context).colorScheme.surfaceContainerHighest,
);
},
),
),
Focus(
onFocusChange: (hasFocus) {
if (hasFocus) template.setCryptoCurrency(true);
},
child: Column(
children: [
Observer(
builder: (context) => CurrencyAmountTextField(
hasUnderlineBorder: true,
borderWidth: 0.0,
selectedCurrency: template.selectedCurrency.title,
selectedCurrencyDecimals: template.selectedCurrency.decimals,
amountFocusNode: _cryptoAmountFocus,
amountController: _cryptoAmountController,
isSelected: template.isCryptoSelected,
tag: template.selectedCurrency.tag,
isPickerEnable: sendTemplateViewModel.hasMultipleTokens,
onTapPicker: () => _presentPicker(context),
currencyValueValidator: sendTemplateViewModel.amountValidator,
isAmountEditable: true,
fillColor: Theme.of(context).colorScheme.surfaceContainerHighest,
),
),
Divider(height: 1, color: Theme.of(context).colorScheme.outlineVariant),
],
),
),
Focus(
onFocusChange: (hasFocus) {
if (hasFocus) template.setCryptoCurrency(false);
},
child: Column(
children: [
Observer(
builder: (context) => CurrencyAmountTextField(
hasUnderlineBorder: true,
borderWidth: 0.0,
selectedCurrency: sendTemplateViewModel.fiatCurrency,
selectedCurrencyDecimals: sendTemplateViewModel.fiatCurrencyDecimals,
amountFocusNode: _fiatAmountFocus,
amountController: _fiatAmountController,
isSelected: !template.isCryptoSelected,
hintText: '0.00',
isAmountEditable: true,
fillColor: Theme.of(context).colorScheme.surfaceContainerHighest,
),
),
Divider(height: 1, color: Theme.of(context).colorScheme.outlineVariant),
],
),
),
],
),
)
],
),
);
}
void _setEffects(BuildContext context) {
if (_effectsInstalled) {
return;
}
final output = template.output;
if (template.address.isNotEmpty) {
_addressController.text = template.address;
}
if (template.name.isNotEmpty) {
_nameController.text = template.name;
}
if (template.output.cryptoAmount.isNotEmpty) {
_cryptoAmountController.text = template.output.cryptoAmount;
}
if (template.output.fiatAmount.isNotEmpty) {
_fiatAmountController.text = template.output.fiatAmount;
}
_addressController.addListener(() {
final address = _addressController.text;
if (template.address != address) {
template.address = address;
}
});
_cryptoAmountController.addListener(() {
final amount = _cryptoAmountController.text;
if (amount != output.cryptoAmount) {
output.setCryptoAmount(amount);
}
});
_fiatAmountController.addListener(() {
final amount = _fiatAmountController.text;
if (amount != output.fiatAmount) {
output.setFiatAmount(amount);
}
});
_nameController.addListener(() {
final name = _nameController.text;
if (name != template.name) {
template.name = name;
}
});
reaction((_) => template.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => output.cryptoAmount, (String amount) {
if (amount != _cryptoAmountController.text) {
_cryptoAmountController.text = amount;
}
});
reaction((_) => output.fiatAmount, (String amount) {
if (amount != _fiatAmountController.text) {
_fiatAmountController.text = amount;
}
});
reaction((_) => template.name, (String name) {
if (name != _nameController.text) {
_nameController.text = name;
}
});
_effectsInstalled = true;
}
void _presentPicker(BuildContext context) {
showPopUp<void>(
context: context,
builder: (_) => CurrencyPicker(
selectedAtIndex: sendTemplateViewModel.walletCurrencies.indexOf(template.selectedCurrency),
items: sendTemplateViewModel.walletCurrencies,
hintText: S.of(context).search_currency,
onItemSelected: (Currency cur) => template.changeSelectedCurrency(cur as CryptoCurrency),
),
);
}
}
-1
View File
@@ -1 +0,0 @@
enum Attributes { arrow, header, link, switcher, widget, rawWidget }
@@ -1,119 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/setting_action_button.dart';
import 'package:cake_wallet/src/widgets/setting_actions.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/router.dart' as Router;
final _settingsNavigatorKey = GlobalKey<NavigatorState>();
class DesktopSettingsPage extends StatefulWidget {
const DesktopSettingsPage(this.dashboardViewModel, {super.key});
final DashboardViewModel dashboardViewModel;
@override
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
}
class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
final int itemCount = SettingActions.all.length;
int? currentPage;
void _onItemChange(int index) {
setState(() {
currentPage = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Theme.of(context).colorScheme.surface,
height: MediaQuery.of(context).size.height,
child: Row(
children: [
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 4),
child: Text(
S.current.settings,
style: textXLarge(),
),
),
Expanded(
child: ListView.separated(
padding: EdgeInsets.only(top: 0),
itemBuilder: (_, index) {
final item = SettingActions.all[index];
if (!widget.dashboardViewModel.hasSilentPayments &&
item.name(context) == S.of(context).silent_payments_settings) {
return Container();
}
if ((!widget.dashboardViewModel.isMoneroViewOnly &&
item.name(context) == S.of(context).export_outputs) ||
(!widget.dashboardViewModel.hasMweb &&
item.name(context) == S.of(context).litecoin_mweb_settings)) {
return Container();
}
final isLastTile = index == itemCount - 1;
return SettingActionButton(
isLastTile: isLastTile,
selectionActive: currentPage != null,
isSelected: currentPage == index,
isArrowVisible: true,
onTap: () {
if (currentPage != index) {
final settingContext =
_settingsNavigatorKey.currentState?.context ?? context;
item.onTap.call(settingContext);
_onItemChange(index);
}
},
image: item.image,
title: item.name.call(context),
);
},
separatorBuilder: (_, index) => Container(
height: 0.0,
color: Theme.of(context).colorScheme.outlineVariant,
),
itemCount: itemCount,
),
),
],
),
),
Flexible(
flex: 2,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Navigator(
key: _settingsNavigatorKey,
initialRoute: Routes.empty_no_route,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
return [
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
),
)
],
),
),
);
}
}
@@ -1,18 +0,0 @@
class ItemHeaders {
static const nodes = 'Nodes';
static const currentNode = 'Current node';
static const wallets = 'Wallets';
static const displayBalanceAs = 'Display balance as';
static const currency = 'Currency';
static const feePriority = 'Fee priority';
static const saveRecipientAddress = 'Save recipient address';
static const personal = 'Personal';
static const changePIN = 'Change PIN';
static const changeLanguage = 'Change language';
static const allowBiometricalAuthentication = 'Allow biometrical authentication';
static const darkMode = 'Dark mode';
static const support = 'Support';
static const termsAndConditions = 'Terms and conditions';
static const faq = 'FAQ';
static const version = 'Version';
}
@@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/screens/settings/attributes.dart';
class SettingsItem {
SettingsItem(
{required this.onTaped,
required this.title,
required this.link,
required this.image,
required this.widget,
required this.attribute,
required this.widgetBuilder});
final VoidCallback onTaped;
final String title;
final String link;
final Image image;
final Widget widget;
final Attributes attribute;
final WidgetBuilder widgetBuilder;
}
@@ -32,14 +32,6 @@ class SilentPaymentsSettingsPage extends StatelessWidget {
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
if (!FeatureFlag.hasNewUi)
SettingsSwitcherCell(
title: S.current.silent_payments_display_card,
value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value);
},
),
SettingsSwitcherCell(
title: S.current.silent_payments_always_scan,
value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan,
@@ -1,109 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class TrocadorProvidersPage extends BasePage {
TrocadorProvidersPage(this.trocadorProvidersViewModel);
@override
String get title => 'Trocador Providers';
final TrocadorProvidersViewModel trocadorProvidersViewModel;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Observer(
builder: (_) {
if (trocadorProvidersViewModel.isLoading) {
return Center(child: CircularProgressIndicator());
}
var providerStates = trocadorProvidersViewModel.providerStates;
final providerRatings = trocadorProvidersViewModel.providerRatings;
if (providerStates.isEmpty) {
return Center(child: Text(S.of(context).no_providers_available));
}
return ListView.builder(
itemCount: providerStates.length,
itemBuilder: (_, index) {
final providerName = providerStates.keys.elementAt(index);
final providerEnabled = providerStates[providerName] ?? true;
return SettingsSwitcherCell(
title: providerName,
value: providerEnabled,
leading: Badge(
title: 'KYC \nRATING',
subTitle: providerRatings[providerName] ?? 'N/A',
textColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
onValueChange: (BuildContext _, value) =>
trocadorProvidersViewModel.toggleProviderState(providerName),
);
},
);
},
),
);
}
}
class Badge extends StatelessWidget {
Badge(
{required this.textColor,
required this.backgroundColor,
required this.title,
required this.subTitle});
final String title;
final String subTitle;
final Color textColor;
final Color backgroundColor;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FittedBox(
fit: BoxFit.fitHeight,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)), color: backgroundColor),
alignment: Alignment.center,
child: IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
title,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: textColor,
fontSize: 7,
fontWeight: FontWeight.w600,
),
),
VerticalDivider(
color: textColor,
thickness: 1,
),
Text(
subTitle,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: textColor,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
);
}
}
@@ -1,79 +0,0 @@
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
SettingsPriorityPickerCell({
required String title,
required this.selectedItem,
required this.items,
this.displayItem,
this.images,
this.searchHintText,
this.isGridView = false,
this.matchingCriteria,
this.customValue,
this.maxValue,
this.customItemIndex,
this.onItemSelected,
}) : super(
title: title,
isSelected: false,
onTap: (BuildContext context) async {
var selectedAtIndex = items.indexOf(selectedItem);
double sliderValue = customValue ?? 0.0;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Picker(
items: items,
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
selectedAtIndex: selectedAtIndex,
customItemIndex: customItemIndex,
maxValue: maxValue,
headerEnabled: false,
closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: sliderValue,
onSliderChanged: (double newValue) =>
setState(() => sliderValue = newValue),
onItemSelected: (ItemType priority) {
setState(() => selectedAtIndex = items.indexOf(priority));
onItemSelected?.call(priority, sliderValue);
},
);
},
);
},
);
onItemSelected?.call(items[selectedAtIndex], sliderValue);
});
final ItemType selectedItem;
final List<ItemType> items;
final void Function(ItemType item, double customValue)? onItemSelected;
final String Function(ItemType item, int value)? displayItem;
final List<Image>? images;
final String? searchHintText;
final bool isGridView;
final bool Function(ItemType, String)? matchingCriteria;
double? customValue;
double? maxValue;
int? customItemIndex;
@override
Widget buildTrailing(BuildContext context) {
return Text(
displayItem?.call(selectedItem, customValue?.round() ?? 0) ?? selectedItem.toString(),
textAlign: TextAlign.right,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
}
@@ -1,9 +0,0 @@
import 'package:flutter/cupertino.dart';
class SettingsPickerRaw extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
@@ -1,27 +0,0 @@
import 'package:flutter/material.dart';
class SettingsVersionCell extends StatelessWidget {
SettingsVersionCell({required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 24),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
title,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.normal,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
);
}
}
@@ -1,42 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/material.dart';
class WalletConnectTile extends StatelessWidget {
const WalletConnectTile({required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/walletconnect_logo.png',
height: 24,
width: 24,
),
SizedBox(width: 16),
Expanded(
child: Text(
S.current.walletConnect,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
),
),
Image.asset(
'assets/images/select_arrow.png',
color: Theme.of(context).colorScheme.onSurfaceVariant,
)
],
),
),
);
}
}
-8
View File
@@ -1,8 +0,0 @@
import 'package:flutter/material.dart';
class SplashPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(body: Container());
}
}
@@ -1,82 +0,0 @@
import 'package:mobx/mobx.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
import 'package:cake_wallet/core/address_label_validator.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class AddressEditOrCreatePage extends BasePage {
AddressEditOrCreatePage({required this.addressEditOrCreateViewModel})
: _formKey = GlobalKey<FormState>(),
_labelController = TextEditingController(),
super() {
_labelController.addListener(() => addressEditOrCreateViewModel.label = _labelController.text);
_labelController.text = addressEditOrCreateViewModel.label;
}
final WalletAddressEditOrCreateViewModel addressEditOrCreateViewModel;
final GlobalKey<FormState> _formKey;
final TextEditingController _labelController;
bool _isEffectsInstalled = false;
@override
String get title => S.current.new_subaddress_title;
@override
Widget body(BuildContext context) {
_setEffects(context);
return Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _labelController,
hintText: S.of(context).new_subaddress_label_name,
validator: AddressLabelValidator(),
),
)),
Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState?.validate() ?? false) {
await addressEditOrCreateViewModel.save();
}
},
text: addressEditOrCreateViewModel.isEdit
? S.of(context).rename
: S.of(context).new_subaddress_create,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: addressEditOrCreateViewModel.state is AddressIsSaving,
isDisabled: addressEditOrCreateViewModel.label.isEmpty,
),
)
],
),
));
}
void _setEffects(BuildContext context) {
if (_isEffectsInstalled) {
return;
}
reaction((_) => addressEditOrCreateViewModel.state, (AddressEditOrCreateState state) {
if (state is AddressSavedSuccessfully) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) Navigator.of(context).pop();
});
}
});
_isEffectsInstalled = true;
}
}
@@ -2,7 +2,6 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro
import 'package:cake_wallet/src/screens/settings/widgets/settings_link_provider_cell.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/view_model/settings/link_list_item.dart';
import 'package:cake_wallet/view_model/settings/regular_list_item.dart';
import 'package:cake_wallet/view_model/support_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@@ -36,9 +35,6 @@ class SupportOtherLinksPage extends BasePage {
itemCounter: (int _) => supportViewModel.items.length,
itemBuilder: (_, index) {
final item = supportViewModel.items[index];
if (item is RegularListItem) {
return SettingsCellWithArrow(title: item.title, handler: item.handler);
}
if (item is LinkListItem) {
bool hasLightIcon = false;
if (item.lightIcon != null) hasLightIcon = true;
@@ -1,217 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
import 'package:cake_wallet/src/widgets/alert_with_no_action.dart.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class UnspentCoinsListPage extends BasePage {
UnspentCoinsListPage({required this.unspentCoinsListViewModel});
@override
String get title => S.current.unspent_coins_title;
@override
Widget leading(BuildContext context) {
return MergeSemantics(
child: SizedBox(
height: 37,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: S.of(context).seed_alert_back,
child: TextButton(
style: ButtonStyle(
overlayColor: WidgetStateProperty.all(Colors.transparent),
),
onPressed: () async => await handleOnPopInvoked(context),
child: backButton(context),
),
),
),
),
);
}
final UnspentCoinsListViewModel unspentCoinsListViewModel;
Future<void> handleOnPopInvoked(BuildContext context) async {
final navigator = Navigator.of(context);
final hasChanged = unspentCoinsListViewModel.hasAdjustableFieldChanged;
if (unspentCoinsListViewModel.items.isEmpty || !hasChanged) {
if (navigator.canPop()) navigator.pop();
return;
}
unspentCoinsListViewModel.setIsDisposing(true);
await unspentCoinsListViewModel.dispose();
if (navigator.canPop()) navigator.pop();
if (navigator.canPop()) navigator.pop();
}
@override
Widget body(BuildContext context) =>
UnspentCoinsListForm(unspentCoinsListViewModel, handleOnPopInvoked);
}
class UnspentCoinsListForm extends StatefulWidget {
UnspentCoinsListForm(this.unspentCoinsListViewModel, this.handleOnPopInvoked);
final UnspentCoinsListViewModel unspentCoinsListViewModel;
final Future<void> Function(BuildContext context) handleOnPopInvoked;
@override
UnspentCoinsListFormState createState() => UnspentCoinsListFormState(unspentCoinsListViewModel);
}
class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
UnspentCoinsListFormState(this.unspentCoinsListViewModel);
final UnspentCoinsListViewModel unspentCoinsListViewModel;
late Future<void> _initialization;
ReactionDisposer? _disposer;
@override
void initState() {
super.initState();
_initialization = unspentCoinsListViewModel.initialSetup();
_setupReactions();
}
void _setupReactions() {
_disposer = reaction<bool>(
(_) => unspentCoinsListViewModel.isDisposing,
(isDisposing) {
if (isDisposing) {
_showSavingDataAlert();
}
},
);
}
void _showSavingDataAlert() {
showDialog<void>(
context: context,
useRootNavigator: false,
builder: (BuildContext context) {
return AlertWithNoAction(
alertContent: 'Updating, please wait…',
alertBarrierDismissible: false,
);
},
);
}
@override
void dispose() {
_disposer?.call();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) async {
if (didPop) return;
if (mounted) await widget.handleOnPopInvoked(context);
},
child: FutureBuilder<void>(
future: _initialization,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.primary,
));
}
if (snapshot.hasError) return Center(child: Text('Failed to load unspent coins'));
return Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: Observer(
builder: (_) => Column(
children: [
if (unspentCoinsListViewModel.items.isNotEmpty)
Row(
children: [
SizedBox(width: 12),
StandardCheckbox(
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
value: unspentCoinsListViewModel.isAllSelected,
onChanged: (value) => unspentCoinsListViewModel.toggleSelectAll(value),
),
SizedBox(width: 12),
Text(
S.current.all_coins,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
SizedBox(height: 15),
Expanded(
child: unspentCoinsListViewModel.items.isEmpty
? Center(
child: Text(
'No unspent coins available',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
))
: ListView.separated(
itemCount: unspentCoinsListViewModel.items.length,
separatorBuilder: (_, __) => SizedBox(height: 15),
itemBuilder: (_, int index) {
final item = unspentCoinsListViewModel.items[index];
return Observer(builder: (_) {
final fiatAmount =
unspentCoinsListViewModel.fiatAmounts[item.amount] ?? '';
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(
Routes.unspentCoinsDetails,
arguments: [item, unspentCoinsListViewModel],
),
child: UnspentCoinsListItem(
note: item.note,
amount: item.amount,
fiatAmount: fiatAmount,
address: item.address,
isSending: item.isSending,
isFrozen: item.isFrozen,
isChange: item.isChange,
isSilentPayment: item.isSilentPayment,
onCheckBoxTap: item.isFrozen
? null
: () async {
item.isSending = !item.isSending;
await unspentCoinsListViewModel
.saveUnspentCoinInfo(item);
},
),
);
});
},
),
),
],
),
),
);
},
),
);
}
}
@@ -1,213 +0,0 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:flutter/material.dart';
class UnspentCoinsListItem extends StatelessWidget {
UnspentCoinsListItem({
required this.note,
required this.amount,
required this.fiatAmount,
required this.address,
required this.isSending,
required this.isFrozen,
required this.isChange,
required this.isSilentPayment,
this.onCheckBoxTap,
});
final String note;
final String amount;
final String fiatAmount;
final String address;
final bool isSending;
final bool isFrozen;
final bool isChange;
final bool isSilentPayment;
final Function()? onCheckBoxTap;
@override
Widget build(BuildContext context) {
final unselectedItemColor = Theme.of(context).colorScheme.surfaceContainer;
final selectedItemColor = Theme.of(context).colorScheme.primary;
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
final amountColor = isSending
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface;
final addressColor = isSending
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface;
return Container(
height: 70,
padding: EdgeInsets.symmetric(vertical: 6, horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: itemColor,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(right: 12),
child: StandardCheckbox(
iconColor: amountColor,
borderColor: addressColor,
value: isSending,
onChanged: (value) => onCheckBoxTap?.call(),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (note.isNotEmpty)
AutoSizeText(
note,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: amountColor,
fontSize: 15,
fontWeight: FontWeight.w600,
),
maxLines: 1,
),
AutoSizeText(
amount,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: amountColor,
fontSize: 15,
fontWeight: FontWeight.w600,
),
maxLines: 1,
)
],
),
if (isFrozen)
Container(
height: 17,
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: Theme.of(context).colorScheme.primary,
),
alignment: Alignment.center,
child: Text(
S.of(context).frozen,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w600,
color: itemColor,
fontSize: 8,
),
),
),
],
),
if (fiatAmount.isNotEmpty)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
fiatAmount,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: amountColor,
fontSize: 1,
fontWeight: FontWeight.w600,
),
maxLines: 1,
),
],
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AutoSizeText(
'${address.substring(0, 5)}...${address.substring(address.length - 5)}', // ToDo: Maybe use address label
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: addressColor,
),
maxLines: 1,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (isChange)
Container(
height: 17,
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: Theme.of(context).colorScheme.primaryContainer,
),
alignment: Alignment.center,
child: Text(
S.of(context).unspent_change,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w600,
fontSize: 8,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
if (address.toLowerCase().contains("mweb"))
Container(
height: 17,
padding: EdgeInsets.only(left: 6, right: 6),
margin: EdgeInsets.only(left: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(8.5),
),
color: Theme.of(context).colorScheme.primaryContainer,
),
alignment: Alignment.center,
child: Text(
"MWEB",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w600,
fontSize: 8,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
if (isSilentPayment)
Container(
height: 17,
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: Theme.of(context).colorScheme.primaryContainer,
),
alignment: Alignment.center,
child: Text(
S.of(context).silent_payments,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w600,
fontSize: 8,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
],
),
],
),
),
],
),
),
],
),
);
}
}
@@ -339,45 +339,26 @@ class WalletListBodyState extends State<WalletListBody> {
Stack(
alignment: Alignment.bottomCenter,
children: [
!FeatureFlag.hasNewUi
? IgnorePointer(
child: Container(
alignment: Alignment.bottomCenter,
height: 185,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Theme.of(context).colorScheme.surface.withAlpha(10),
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surface
],
),
),
),
)
: IgnorePointer(
child: Container(
height: 275,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Theme.of(context).colorScheme.surfaceDim.withAlpha(10),
Theme.of(context).colorScheme.surfaceDim.withAlpha(150),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255)
],
),
),
),
IgnorePointer(
child: Container(
height: 275,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Theme.of(context).colorScheme.surfaceDim.withAlpha(10),
Theme.of(context).colorScheme.surfaceDim.withAlpha(150),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255)
],
),
),
),
),
Container(
height: 240,
width: MediaQuery.of(context).size.width,
@@ -464,7 +445,7 @@ class WalletListBodyState extends State<WalletListBody> {
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
if (FeatureFlag.hasNewUi) SizedBox(height: 52.0)
SizedBox(height: 52.0)
],
),
),

Some files were not shown because too many files have changed in this diff Show More