mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
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:
@@ -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;
|
||||
}
|
||||
@@ -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] ?? ''
|
||||
: '';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
))
|
||||
],
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -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 +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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_page_indicator.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class FirstIntroduction extends StatelessWidget {
|
||||
FirstIntroduction({required this.onNext, this.onClose});
|
||||
|
||||
static const aspectRatioImage = 1.133;
|
||||
final VoidCallback? onClose;
|
||||
final VoidCallback onNext;
|
||||
final animation = Lottie.asset('assets/animation/anim1.json');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Container(
|
||||
height: screenHeight,
|
||||
width: screenWidth,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(top: 40, bottom: 40),
|
||||
content: Column(children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: YatBar(onClose: () => Navigator.of(context).pop())),
|
||||
animation,
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 30, right: 30),
|
||||
child: Column(children: [
|
||||
Text(S.of(context).yat_alert_title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.none,
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Text(S.of(context).yat_alert_content,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.none,
|
||||
)))
|
||||
]))
|
||||
]),
|
||||
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
bottomSection: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
PrimaryButton(
|
||||
text: S.of(context).restore_next,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: onNext),
|
||||
Padding(padding: EdgeInsets.only(top: 24), child: YatPageIndicator(filled: 0))
|
||||
]),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_page_indicator.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class SecondIntroduction extends StatelessWidget {
|
||||
SecondIntroduction({required this.onNext, this.onClose});
|
||||
|
||||
final VoidCallback? onClose;
|
||||
final VoidCallback onNext;
|
||||
final animation = Lottie.asset('assets/animation/anim2.json');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Container(
|
||||
height: screenHeight,
|
||||
width: screenWidth,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(top: 40, bottom: 40),
|
||||
content: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: YatBar(onClose: onClose)),
|
||||
animation,
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 40, left: 30, right: 30),
|
||||
child: Column(children: [
|
||||
Text(S.of(context).second_intro_title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.none,
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Text(S.of(context).second_intro_content,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.none,
|
||||
)))
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
bottomSection: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
PrimaryButton(
|
||||
text: S.of(context).restore_next,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: onNext),
|
||||
Padding(padding: EdgeInsets.only(top: 24), child: YatPageIndicator(filled: 1))
|
||||
]),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_page_indicator.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class ThirdIntroduction extends StatelessWidget {
|
||||
ThirdIntroduction({required this.onGet, required this.onConnect, this.onClose});
|
||||
|
||||
final VoidCallback? onClose;
|
||||
final VoidCallback onGet;
|
||||
final VoidCallback onConnect;
|
||||
final animation = Lottie.asset('assets/animation/anim3.json');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Container(
|
||||
height: screenHeight,
|
||||
width: screenWidth,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(top: 40, bottom: 40),
|
||||
content: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 90,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: YatBar(onClose: onClose)),
|
||||
animation,
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 40, left: 30, right: 30),
|
||||
child: Column(children: [
|
||||
Text(S.of(context).third_intro_title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.none,
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Text(S.of(context).third_intro_content,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.none,
|
||||
)))
|
||||
])),
|
||||
],
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
bottomSection: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
PrimaryIconButton(
|
||||
text: S.of(context).get_your_yat,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderColor: Theme.of(context).colorScheme.primary,
|
||||
iconColor: Theme.of(context).colorScheme.onPrimary,
|
||||
iconBackgroundColor: Colors.transparent,
|
||||
iconData: CupertinoIcons.arrow_up_right_square,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
onPressed: onGet,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: PrimaryIconButton(
|
||||
text: S.of(context).connect_an_existing_yat,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderColor: Theme.of(context).colorScheme.primary,
|
||||
iconColor: Theme.of(context).colorScheme.onPrimary,
|
||||
iconBackgroundColor: Colors.transparent,
|
||||
iconData: CupertinoIcons.arrow_up_right_square,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
onPressed: onConnect,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: YatPageIndicator(filled: 2),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_close_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class YatBar extends StatelessWidget {
|
||||
YatBar({this.onClose});
|
||||
|
||||
final VoidCallback? onClose;
|
||||
final image = Image.asset('assets/images/yat_logo.png', width: 81, height: 28);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(alignment: Alignment.bottomCenter, children: [
|
||||
Positioned(top: 0, right: 0, child: YatCloseButton(onClose: onClose)),
|
||||
Positioned(top: 16, child: image)
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class YatCloseButton extends StatelessWidget {
|
||||
YatCloseButton({this.onClose});
|
||||
|
||||
final VoidCallback? onClose;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onClose,
|
||||
child: Container(
|
||||
height: 28,
|
||||
width: 28,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.clear,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class YatPageIndicator extends StatelessWidget {
|
||||
YatPageIndicator({required this.filled});
|
||||
|
||||
final int filled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 44,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(3, (index) {
|
||||
final size = 8.0;
|
||||
final isFilled = index == filled;
|
||||
|
||||
return Container(
|
||||
height: size,
|
||||
width: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isFilled
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.1)));
|
||||
})));
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
import 'package:cake_wallet/core/address_resolver/yat/yat_store.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/first_introduction.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/second_introduction.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/third_introduction.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_close_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animate_do/animate_do.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class YatPopup extends StatelessWidget {
|
||||
YatPopup({required this.dashboardViewModel, required this.onClose})
|
||||
: baseUrl = YatLink.isDevMode ? YatLink.baseDevUrl : YatLink.baseReleaseUrl;
|
||||
|
||||
static const durationInMilliseconds = 250;
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final VoidCallback onClose;
|
||||
final String baseUrl;
|
||||
final image = Image.asset('assets/images/emoji_popup.png');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
AlertBackground(child: Container()),
|
||||
SlideInUp(
|
||||
from: 420,
|
||||
duration: Duration(milliseconds: durationInMilliseconds),
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)),
|
||||
child: Container(
|
||||
height: 420,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
padding: EdgeInsets.fromLTRB(24, 15, 24, 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [YatCloseButton(onClose: onClose)]),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
height: 64,
|
||||
width: 165,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.all(Radius.circular(32)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.05),
|
||||
blurRadius: 15,
|
||||
offset: Offset(0, 5))
|
||||
]),
|
||||
child: image)
|
||||
])
|
||||
]),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
child: Column(children: [
|
||||
Text(S.of(context).yat_popup_title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Text(S.of(context).yat_popup_content,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 16,
|
||||
decoration: TextDecoration.none,
|
||||
)))
|
||||
])),
|
||||
PrimaryButton(
|
||||
text: S.of(context).learn_more,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () => dashboardViewModel.isShowFirstYatIntroduction = true)
|
||||
],
|
||||
))),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => dashboardViewModel.isShowFirstYatIntroduction
|
||||
? SlideInRight(
|
||||
from: screenWidth,
|
||||
duration: Duration(milliseconds: durationInMilliseconds),
|
||||
child: FirstIntroduction(
|
||||
onClose: onClose,
|
||||
onNext: () => dashboardViewModel.isShowSecondYatIntroduction = true))
|
||||
: Container()),
|
||||
Observer(
|
||||
builder: (_) => dashboardViewModel.isShowSecondYatIntroduction
|
||||
? SlideInRight(
|
||||
from: screenWidth,
|
||||
duration: Duration(milliseconds: durationInMilliseconds),
|
||||
child: SecondIntroduction(
|
||||
onClose: onClose,
|
||||
onNext: () => dashboardViewModel.isShowThirdYatIntroduction = true))
|
||||
: Container()),
|
||||
Observer(
|
||||
builder: (_) => dashboardViewModel.isShowThirdYatIntroduction
|
||||
? SlideInRight(
|
||||
from: screenWidth,
|
||||
duration: Duration(milliseconds: durationInMilliseconds),
|
||||
child: ThirdIntroduction(
|
||||
onClose: onClose,
|
||||
onGet: () {
|
||||
var createNewYatUrl = YatLink.startFlowUrl;
|
||||
final createNewYatUrlParameters =
|
||||
dashboardViewModel.yatStore.defineQueryParameters();
|
||||
|
||||
if (createNewYatUrlParameters.isNotEmpty) {
|
||||
createNewYatUrl += '?sub1=' + createNewYatUrlParameters;
|
||||
}
|
||||
|
||||
final uri = Uri.parse(createNewYatUrl);
|
||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
},
|
||||
onConnect: () {
|
||||
String url = baseUrl + YatLink.signInSuffix;
|
||||
final parameters = dashboardViewModel.yatStore.defineQueryParameters();
|
||||
if (parameters.isNotEmpty) {
|
||||
url += YatLink.queryParameter + parameters;
|
||||
}
|
||||
final uri = Uri.parse(url);
|
||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}))
|
||||
: Container())
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import 'package:cake_wallet/src/screens/yat/widgets/yat_close_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animate_do/animate_do.dart';
|
||||
|
||||
class YatEmojiId extends StatelessWidget {
|
||||
YatEmojiId(this.emojiId);
|
||||
static const durationInMilliseconds = 250;
|
||||
|
||||
final String emojiId;
|
||||
final image = Image.asset('assets/images/emoji_popup.png');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
AlertBackground(child: Container()),
|
||||
SlideInUp(
|
||||
from: 420,
|
||||
duration: Duration(milliseconds: durationInMilliseconds),
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)),
|
||||
child: Container(
|
||||
height: 420,
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
padding: EdgeInsets.fromLTRB(24, 15, 24, 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [YatCloseButton(onClose: () => Navigator.of(context).pop())]),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50))),
|
||||
child: Text(emojiId,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 46,
|
||||
decoration: TextDecoration.none,
|
||||
)))
|
||||
])
|
||||
]),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
child: Column(children: [
|
||||
Text("That's one nice Yat!",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Text(
|
||||
'You can manage your Yat or purchase additional Yats in your account settings',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
decoration: TextDecoration.none,
|
||||
)))
|
||||
])),
|
||||
PrimaryButton(
|
||||
text: 'Got it',
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () => Navigator.of(context).pop())
|
||||
],
|
||||
))),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
const _firstLayoutMaxHeight = 10000.0;
|
||||
|
||||
class PageViewHeightAdaptable extends StatefulWidget {
|
||||
const PageViewHeightAdaptable({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.children,
|
||||
}) : assert(children.length > 0, 'children must not be empty');
|
||||
|
||||
final PageController controller;
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
State<PageViewHeightAdaptable> createState() => _PageViewHeightAdaptableState();
|
||||
}
|
||||
|
||||
class _PageViewHeightAdaptableState extends State<PageViewHeightAdaptable> {
|
||||
final _sizes = <int, Size>{};
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PageViewHeightAdaptable oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
_sizes.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.controller,
|
||||
builder: (context, child) => _SizingContainer(
|
||||
sizes: _sizes,
|
||||
page: widget.controller.hasClients ? widget.controller.page ?? 0 : 0,
|
||||
child: child!,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => PageView(
|
||||
controller: widget.controller,
|
||||
children: [
|
||||
for (final (i, child) in widget.children.indexed)
|
||||
Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
children: [
|
||||
SizedBox.fromSize(size: _sizes[i]),
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: _SizeAware(
|
||||
child: child,
|
||||
// don't setState, we'll use it in the layout phase
|
||||
onSizeLaidOut: (size) {
|
||||
_sizes[i] = size;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef _OnSizeLaidOutCallback = void Function(Size);
|
||||
|
||||
class _SizingContainer extends SingleChildRenderObjectWidget {
|
||||
const _SizingContainer({
|
||||
super.child,
|
||||
required this.sizes,
|
||||
required this.page,
|
||||
});
|
||||
|
||||
final Map<int, Size> sizes;
|
||||
final double page;
|
||||
|
||||
@override
|
||||
_RenderSizingContainer createRenderObject(BuildContext context) {
|
||||
return _RenderSizingContainer(
|
||||
sizes: sizes,
|
||||
page: page,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
_RenderSizingContainer renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..sizes = sizes
|
||||
..page = page;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderSizingContainer extends RenderProxyBox {
|
||||
_RenderSizingContainer({
|
||||
RenderBox? child,
|
||||
required Map<int, Size> sizes,
|
||||
required double page,
|
||||
}) : _sizes = sizes,
|
||||
_page = page,
|
||||
super(child);
|
||||
|
||||
Map<int, Size> _sizes;
|
||||
Map<int, Size> get sizes => _sizes;
|
||||
set sizes(Map<int, Size> value) {
|
||||
if (_sizes == value) return;
|
||||
_sizes = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
double _page;
|
||||
double get page => _page;
|
||||
set page(double value) {
|
||||
if (_page == value) return;
|
||||
_page = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
if (child case final child?) {
|
||||
child.layout(
|
||||
constraints.copyWith(
|
||||
minWidth: constraints.maxWidth,
|
||||
minHeight: 0,
|
||||
maxHeight: constraints.hasBoundedHeight ? null : _firstLayoutMaxHeight,
|
||||
),
|
||||
parentUsesSize: true,
|
||||
);
|
||||
|
||||
final int floorPage = page.floor();
|
||||
final int ceilPage = page.ceil();
|
||||
|
||||
Size? a = sizes[floorPage];
|
||||
Size? b = sizes[ceilPage];
|
||||
|
||||
if (a == null && sizes.isNotEmpty) {
|
||||
a = sizes.values.first;
|
||||
}
|
||||
if (b == null && sizes.isNotEmpty) {
|
||||
b = sizes.values.first;
|
||||
}
|
||||
|
||||
a ??= child.getDryLayout(constraints);
|
||||
b ??= a;
|
||||
|
||||
final double t = (page - floorPage).clamp(0.0, 1.0);
|
||||
final double height = lerpDouble(a.height, b.height, t) ?? a.height;
|
||||
|
||||
child.layout(
|
||||
constraints.copyWith(minHeight: height, maxHeight: height),
|
||||
parentUsesSize: true,
|
||||
);
|
||||
size = child.size;
|
||||
} else {
|
||||
size = computeSizeForNoChild(constraints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SizeAware extends SingleChildRenderObjectWidget {
|
||||
const _SizeAware({
|
||||
required Widget child,
|
||||
required this.onSizeLaidOut,
|
||||
}) : super(child: child);
|
||||
|
||||
final _OnSizeLaidOutCallback onSizeLaidOut;
|
||||
|
||||
@override
|
||||
_RenderSizeAware createRenderObject(BuildContext context) {
|
||||
return _RenderSizeAware(
|
||||
onSizeLaidOut: onSizeLaidOut,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, _RenderSizeAware renderObject) {
|
||||
renderObject.onSizeLaidOut = onSizeLaidOut;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderSizeAware extends RenderProxyBox {
|
||||
_RenderSizeAware({
|
||||
RenderBox? child,
|
||||
required _OnSizeLaidOutCallback onSizeLaidOut,
|
||||
}) : _onSizeLaidOut = onSizeLaidOut,
|
||||
super(child);
|
||||
|
||||
_OnSizeLaidOutCallback? _onSizeLaidOut;
|
||||
_OnSizeLaidOutCallback get onSizeLaidOut => _onSizeLaidOut!;
|
||||
set onSizeLaidOut(_OnSizeLaidOutCallback value) {
|
||||
if (_onSizeLaidOut == value) return;
|
||||
_onSizeLaidOut = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
super.performLayout();
|
||||
|
||||
onSizeLaidOut(
|
||||
getDryLayout(
|
||||
constraints.copyWith(maxHeight: double.infinity),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AddTemplateButton extends StatelessWidget {
|
||||
final Function() onTap;
|
||||
final int currentTemplatesLength;
|
||||
|
||||
const AddTemplateButton({Key? key, required this.onTap, required this.currentTemplatesLength})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 1, right: 10),
|
||||
child: DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
dashPattern: [6, 4],
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
strokeWidth: 2,
|
||||
radius: Radius.circular(12),
|
||||
child: Container(
|
||||
height: 34,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: responsiveLayoutUtil.shouldRenderMobileUI ? 10 : 30,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
color: Colors.transparent,
|
||||
),
|
||||
child: currentTemplatesLength >= 1
|
||||
? Icon(
|
||||
Icons.add,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
)
|
||||
: Text(
|
||||
S.of(context).new_template,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||
|
||||
class AlertWithNoAction extends BaseAlertDialog {
|
||||
AlertWithNoAction({
|
||||
this.alertTitle,
|
||||
required this.alertContent,
|
||||
this.alertBarrierDismissible = true,
|
||||
Key? key,
|
||||
});
|
||||
|
||||
final String? alertTitle;
|
||||
final String alertContent;
|
||||
final bool alertBarrierDismissible;
|
||||
|
||||
@override
|
||||
String? get titleText => alertTitle;
|
||||
|
||||
@override
|
||||
String get contentText => alertContent;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => alertBarrierDismissible;
|
||||
|
||||
@override
|
||||
bool get isBottomDividerExists => false;
|
||||
|
||||
@override
|
||||
Widget actionButtons(BuildContext context) => Container();
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||
|
||||
class AlertWithPickerOption extends BaseAlertDialog {
|
||||
AlertWithPickerOption(
|
||||
{required this.alertTitle,
|
||||
required this.alertTitleTextSize,
|
||||
required this.alertSubtitle,
|
||||
required this.options,
|
||||
this.onOptionSelected,
|
||||
this.alertBarrierDismissible = true,
|
||||
Key? key});
|
||||
|
||||
final String alertTitle;
|
||||
final double alertTitleTextSize;
|
||||
final String alertSubtitle;
|
||||
final List<Map<String, String>> options;
|
||||
final ValueChanged<Map<String, String>>? onOptionSelected;
|
||||
final bool alertBarrierDismissible;
|
||||
|
||||
@override
|
||||
String get titleText => alertTitle;
|
||||
|
||||
@override
|
||||
double? get titleTextSize => alertTitleTextSize;
|
||||
|
||||
@override
|
||||
String get contentText => alertSubtitle;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => alertBarrierDismissible;
|
||||
|
||||
@override
|
||||
Widget actionButtons(BuildContext context) => Container();
|
||||
|
||||
@override
|
||||
bool get isBottomDividerExists => false;
|
||||
|
||||
@override
|
||||
Widget content(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
contentText,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||
itemCount: options.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = options[index];
|
||||
final displayKey = item['displayKey'] ?? '';
|
||||
final displayValue = item['displayValue'] ?? '';
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
onOptionSelected?.call(item);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Theme.of(context).colorScheme.surface),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
displayKey,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
displayValue,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -299,14 +299,12 @@ Widget _buildBarcodeSide(BuildContext context, {required String cardNumber, requ
|
||||
children: [
|
||||
Text('Gift Card Number',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color.fromRGBO(207, 207, 207, 1))),
|
||||
const SizedBox(height: 4),
|
||||
Text(cardNumber,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color.fromRGBO(146, 146, 146, 1))),
|
||||
@@ -317,14 +315,12 @@ Widget _buildBarcodeSide(BuildContext context, {required String cardNumber, requ
|
||||
children: [
|
||||
Text('PIN Number',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color.fromRGBO(207, 207, 207, 1))),
|
||||
const SizedBox(height: 4),
|
||||
Text(pin,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color.fromRGBO(146, 146, 146, 1))),
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/generate_name.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class PaymentConfirmationBottomSheet extends BaseBottomSheet {
|
||||
PaymentConfirmationBottomSheet({
|
||||
Key? key,
|
||||
required this.paymentFlowResult,
|
||||
required this.paymentViewModel,
|
||||
required this.walletSwitcherViewModel,
|
||||
required this.paymentRequest,
|
||||
required this.onSelectWallet,
|
||||
required this.onChangeWallet,
|
||||
required this.onSwap,
|
||||
this.onSwitchNetwork,
|
||||
}) : super(
|
||||
titleText: '',
|
||||
footerType: FooterType.none,
|
||||
maxHeight: 900,
|
||||
);
|
||||
|
||||
final PaymentFlowResult paymentFlowResult;
|
||||
final PaymentViewModel paymentViewModel;
|
||||
final WalletSwitcherViewModel walletSwitcherViewModel;
|
||||
final PaymentRequest paymentRequest;
|
||||
final VoidCallback onSelectWallet;
|
||||
final VoidCallback onChangeWallet;
|
||||
final void Function(BuildContext) onSwap;
|
||||
final VoidCallback? onSwitchNetwork;
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
return _PaymentConfirmationContent(
|
||||
paymentFlowResult: paymentFlowResult,
|
||||
paymentViewModel: paymentViewModel,
|
||||
walletSwitcherViewModel: walletSwitcherViewModel,
|
||||
paymentRequest: paymentRequest,
|
||||
onSelectWallet: onSelectWallet,
|
||||
onChangeWallet: onChangeWallet,
|
||||
onSwap: onSwap,
|
||||
onSwitchNetwork: onSwitchNetwork,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PaymentConfirmationContent extends StatelessWidget {
|
||||
const _PaymentConfirmationContent({
|
||||
required this.paymentFlowResult,
|
||||
required this.paymentViewModel,
|
||||
required this.walletSwitcherViewModel,
|
||||
required this.paymentRequest,
|
||||
required this.onSelectWallet,
|
||||
required this.onChangeWallet,
|
||||
required this.onSwap,
|
||||
this.onSwitchNetwork,
|
||||
});
|
||||
|
||||
final PaymentFlowResult paymentFlowResult;
|
||||
final PaymentViewModel paymentViewModel;
|
||||
final WalletSwitcherViewModel walletSwitcherViewModel;
|
||||
final PaymentRequest paymentRequest;
|
||||
final VoidCallback onSelectWallet;
|
||||
final VoidCallback onChangeWallet;
|
||||
final void Function(BuildContext) onSwap;
|
||||
final VoidCallback? onSwitchNetwork;
|
||||
|
||||
/// Checks if the given address is a MWEB or SP (Silent Payment) address
|
||||
bool _isMwebOrSpAddress(String address) {
|
||||
if (address.isEmpty) return false;
|
||||
|
||||
final lowerAddress = address.toLowerCase();
|
||||
|
||||
// Check for MWEB addresses (Litecoin MWEB addresses start with "ltcmweb1")
|
||||
if (lowerAddress.startsWith('ltcmweb1')) return true;
|
||||
|
||||
// Check for Silent Payment addresses (Bitcoin SP addresses start with "sp1", "tsp1")
|
||||
if (lowerAddress.startsWith('sp1') || lowerAddress.startsWith('tsp1')) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Checks if flow type is token selection (EVM, Solana, or Tron)
|
||||
bool _isTokenSelectionFlow(PaymentFlowType type) {
|
||||
return type == PaymentFlowType.evmNetworkSelection ||
|
||||
type == PaymentFlowType.solanaTokenSelection ||
|
||||
type == PaymentFlowType.tronTokenSelection;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final currencyName = walletTypeToString(paymentViewModel.detectedWalletType!);
|
||||
final currentWalletName = isEVMCompatibleChain(paymentViewModel.currentWalletType)
|
||||
? evm!.getChainNameByChainId(paymentViewModel.currentChainId!)
|
||||
: walletTypeToString(paymentViewModel.currentWalletType);
|
||||
|
||||
final hasSingleWallet = paymentFlowResult.type == PaymentFlowType.singleWallet ||
|
||||
paymentFlowResult.wallets.length == 1;
|
||||
|
||||
final hasMultipleWallets = paymentFlowResult.type == PaymentFlowType.multipleWallets ||
|
||||
paymentFlowResult.wallets.length > 1;
|
||||
|
||||
final noAvailableWallets = paymentFlowResult.type == PaymentFlowType.noWallets ||
|
||||
paymentFlowResult.wallets.isEmpty;
|
||||
|
||||
final hasAtLeastOneWallet = hasSingleWallet || hasMultipleWallets;
|
||||
|
||||
final isMwebOrSpAddress =
|
||||
_isMwebOrSpAddress(paymentFlowResult.addressDetectionResult?.address ?? '');
|
||||
|
||||
/// If the wallet is EVM but the detected chainId is different from the currently selected chainId
|
||||
final isEVMWalletButDifferentChainId =
|
||||
paymentFlowResult.type == PaymentFlowType.evmNetworkSelection &&
|
||||
paymentFlowResult.wallet != null &&
|
||||
isEVMCompatibleChain(paymentViewModel.currentWalletType);
|
||||
|
||||
final otherWalletsCount = paymentFlowResult.wallets.length;
|
||||
final hasMultipleOtherWallets = otherWalletsCount > 1;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (_isTokenSelectionFlow(paymentFlowResult.type)) ...[
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Image.asset(
|
||||
paymentFlowResult.addressDetectionResult?.detectedCurrency?.iconPath ?? '',
|
||||
width: 70,
|
||||
height: 70,
|
||||
errorBuilder: (context, error, stackTrace) => Icon(
|
||||
Icons.currency_bitcoin,
|
||||
size: 70,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Image.asset(
|
||||
getCryptoCurrencyIconForWalletListItem(
|
||||
paymentViewModel.detectedWalletType!,
|
||||
chainId: paymentViewModel.detectedChainId,
|
||||
),
|
||||
width: 32,
|
||||
height: 32,
|
||||
errorBuilder: (context, error, stackTrace) => Icon(
|
||||
Icons.account_balance_wallet,
|
||||
size: 32,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'${paymentFlowResult.addressDetectionResult?.detectedCurrency} (${walletTypeToString(paymentFlowResult.walletType!)})',
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
letterSpacing: 0.0,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap ${currentWalletName.capitalized()} for ${paymentFlowResult.addressDetectionResult?.detectedCurrency} (${walletTypeToString(paymentFlowResult.walletType!)}) for this payment?''',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
letterSpacing: 0.0,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
Image.asset(
|
||||
getCryptoCurrencyIconForWalletListItem(
|
||||
paymentViewModel.detectedWalletType!,
|
||||
chainId: paymentViewModel.detectedChainId,
|
||||
),
|
||||
width: 118,
|
||||
height: 118,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'$currencyName ${S.current.address_detected.toLowerCase()}',
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
letterSpacing: 0.0,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'''Looks like you scanned a $currencyName address.\n\n'''
|
||||
'''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap ${currentWalletName.capitalized()} for $currencyName for this payment?''',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
letterSpacing: 0.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 72),
|
||||
if (isEVMWalletButDifferentChainId) ...[
|
||||
if (!isMwebOrSpAddress) ...[
|
||||
PrimaryButton(
|
||||
onPressed: () => onSwap(context),
|
||||
text: 'Create In App Swap',
|
||||
color: hasAtLeastOneWallet
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
textColor: hasAtLeastOneWallet
|
||||
? Theme.of(context).colorScheme.onSecondaryContainer
|
||||
: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
if (onSwitchNetwork != null) ...[
|
||||
PrimaryButton(
|
||||
onPressed: onSwitchNetwork,
|
||||
text:
|
||||
'Switch to ${evm!.getChainNameByChainId(paymentViewModel.detectedChainId!).toUpperCase()} Network',
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
if (hasAtLeastOneWallet) ...[
|
||||
PrimaryButton(
|
||||
onPressed: hasMultipleOtherWallets ? onSelectWallet : onChangeWallet,
|
||||
text: S.current.change_wallet_alert_title,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
] else if (hasAtLeastOneWallet) ...[
|
||||
if (!isMwebOrSpAddress) ...[
|
||||
PrimaryButton(
|
||||
onPressed: () => onSwap(context),
|
||||
text: '${S.current.swap} ${currentWalletName.capitalized()}',
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
PrimaryButton(
|
||||
onPressed: hasMultipleWallets ? onSelectWallet : onChangeWallet,
|
||||
text: S.current.switch_wallet,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
] else ...[
|
||||
PrimaryButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
text: S.current.cancel,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (!isMwebOrSpAddress) ...[
|
||||
PrimaryButton(
|
||||
onPressed: () => onSwap(context),
|
||||
text: '${S.current.swap} ${currentWalletName.capitalized()}',
|
||||
color: hasAtLeastOneWallet
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
textColor: hasAtLeastOneWallet
|
||||
? Theme.of(context).colorScheme.onSecondaryContainer
|
||||
: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,497 +0,0 @@
|
||||
import 'package:cake_wallet/core/universal_address_detector.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/qr_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/utils/token_utilities.dart';
|
||||
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
|
||||
import 'package:cake_wallet/wallet_types.g.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TokenSelectionBottomSheet extends BaseBottomSheet {
|
||||
TokenSelectionBottomSheet({
|
||||
Key? key,
|
||||
required this.paymentViewModel,
|
||||
required this.paymentRequest,
|
||||
required this.onNext,
|
||||
this.fixedNetwork,
|
||||
}) : super(
|
||||
titleText: '',
|
||||
footerType: FooterType.none,
|
||||
maxHeight: 900,
|
||||
);
|
||||
|
||||
final PaymentViewModel paymentViewModel;
|
||||
final PaymentRequest paymentRequest;
|
||||
final Function(PaymentFlowResult) onNext;
|
||||
final WalletType? fixedNetwork;
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
return _TokenSelectionContent(
|
||||
paymentViewModel: paymentViewModel,
|
||||
paymentRequest: paymentRequest,
|
||||
onNext: onNext,
|
||||
fixedNetwork: fixedNetwork,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TokenSelectionContent extends StatefulWidget {
|
||||
const _TokenSelectionContent({
|
||||
required this.paymentViewModel,
|
||||
required this.paymentRequest,
|
||||
required this.onNext,
|
||||
this.fixedNetwork,
|
||||
});
|
||||
|
||||
final PaymentViewModel paymentViewModel;
|
||||
final PaymentRequest paymentRequest;
|
||||
final Function(PaymentFlowResult) onNext;
|
||||
final WalletType? fixedNetwork;
|
||||
|
||||
@override
|
||||
State<_TokenSelectionContent> createState() => _TokenSelectionContentState();
|
||||
}
|
||||
|
||||
class _TokenSelectionContentState extends State<_TokenSelectionContent> {
|
||||
WalletType? selectedNetwork;
|
||||
CryptoCurrency? selectedToken;
|
||||
bool isLoadingTokens = false;
|
||||
String? tokenLoadError;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final baseNetwork = widget.fixedNetwork ?? WalletType.ethereum;
|
||||
selectedNetwork = _resolveGenericETHDetectionResultToSpecificChain(
|
||||
baseNetwork, widget.paymentRequest.scheme.isNotEmpty);
|
||||
_autoSelectToken();
|
||||
}
|
||||
|
||||
bool get _isNetworkSelectable =>
|
||||
widget.fixedNetwork == null || isEVMCompatibleChain(widget.fixedNetwork!);
|
||||
|
||||
CryptoCurrency? _tokenMatchingContract(List<CryptoCurrency> tokens, String? rawContract) {
|
||||
final trimmed = rawContract?.trim();
|
||||
if (trimmed == null || trimmed.isEmpty) return null;
|
||||
|
||||
for (final token in tokens) {
|
||||
if (token is Erc20Token && token.contractAddress.toLowerCase() == trimmed.toLowerCase()) {
|
||||
return token;
|
||||
} else if (token is SPLToken && token.mintAddress == trimmed) {
|
||||
return token;
|
||||
} else if (token is TronToken && token.contractAddress == trimmed) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WalletType _resolveGenericETHDetectionResultToSpecificChain(
|
||||
WalletType network, bool hasURIScheme) {
|
||||
if (hasURIScheme || network != WalletType.ethereum) return network;
|
||||
|
||||
final current = widget.paymentViewModel.currentWalletType;
|
||||
if (isEVMCompatibleChain(current)) return current;
|
||||
return network;
|
||||
}
|
||||
|
||||
Future<void> _autoSelectToken() async {
|
||||
final initialNetwork = selectedNetwork;
|
||||
if (initialNetwork == null || !mounted) return;
|
||||
|
||||
final network = _resolveGenericETHDetectionResultToSpecificChain(
|
||||
initialNetwork, widget.paymentRequest.scheme.isNotEmpty);
|
||||
|
||||
setState(() {
|
||||
selectedNetwork = network;
|
||||
isLoadingTokens = true;
|
||||
tokenLoadError = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final tokens = await TokenUtilities.getAvailableTokensForNetwork(network);
|
||||
if (!mounted) return;
|
||||
|
||||
if (tokens.isEmpty) {
|
||||
setState(() {
|
||||
tokenLoadError = 'No tokens available';
|
||||
isLoadingTokens = false;
|
||||
selectedToken = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final matchedToken =
|
||||
_tokenMatchingContract(tokens, widget.paymentRequest.contractAddress) ?? tokens.first;
|
||||
|
||||
setState(() {
|
||||
selectedToken = matchedToken;
|
||||
isLoadingTokens = false;
|
||||
tokenLoadError = null;
|
||||
});
|
||||
} catch (e) {
|
||||
printV('Auto-select token error: $e');
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
tokenLoadError = 'Failed to load tokens';
|
||||
isLoadingTokens = false;
|
||||
selectedToken = walletTypeToCryptoCurrency(network);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _getNetworkTitle() {
|
||||
if (selectedNetwork == null) return S.current.select_network;
|
||||
return walletTypeToString(selectedNetwork!);
|
||||
}
|
||||
|
||||
String _getEcosystemTitle() {
|
||||
if (selectedNetwork == WalletType.solana) {
|
||||
return 'Solana\n${S.current.address_detected.toLowerCase()}';
|
||||
}
|
||||
if (selectedNetwork == WalletType.tron) {
|
||||
return 'Tron\n${S.current.address_detected.toLowerCase()}';
|
||||
}
|
||||
return '${S.current.ethereum_ecosystem}\n${S.current.address_detected.toLowerCase()}';
|
||||
}
|
||||
|
||||
String _getEcosystemDescription() {
|
||||
if (selectedNetwork == WalletType.solana) {
|
||||
return 'Select a token to send on Solana network';
|
||||
}
|
||||
if (selectedNetwork == WalletType.tron) {
|
||||
return 'Select a token to send on Tron network';
|
||||
}
|
||||
return S.current.evm_ecosystem_description;
|
||||
}
|
||||
|
||||
String _getNetworkIcon() {
|
||||
if (selectedNetwork == null) return 'assets/images/eth_chain_mono.svg';
|
||||
return getChainMonoImage(selectedNetwork!);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: _getNetworkIcon(),
|
||||
width: 50,
|
||||
height: 50,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
_getEcosystemTitle(),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
Text(
|
||||
_getEcosystemDescription(),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
letterSpacing: 0.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
TokenSelectionTileWidget(
|
||||
value: _getNetworkTitle(),
|
||||
imagePath: selectedNetwork != null ? getChainMonoImage(selectedNetwork!) : null,
|
||||
color: selectedNetwork != null ? Theme.of(context).colorScheme.primary : null,
|
||||
enabled: _isNetworkSelectable,
|
||||
onTap: _isNetworkSelectable ? () => _showNetworkSelection(context) : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TokenSelectionTileWidget(
|
||||
imagePath: selectedToken?.iconPath,
|
||||
value: selectedToken != null ? selectedToken!.title : S.current.select_token,
|
||||
enabled: selectedNetwork != null && !isLoadingTokens,
|
||||
onTap: isLoadingTokens ? null : () => _showTokenSelection(context),
|
||||
color: selectedToken == null ? Theme.of(context).colorScheme.primary : null,
|
||||
),
|
||||
if (isLoadingTokens) ...[
|
||||
const SizedBox(height: 16),
|
||||
const CircularProgressIndicator(),
|
||||
],
|
||||
if (tokenLoadError != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
tokenLoadError!,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 32),
|
||||
PrimaryButton(
|
||||
text: S.current.restore_next,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
onPressed: selectedNetwork != null && selectedToken != null && !isLoadingTokens
|
||||
? () async => await _handleNext(context)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showNetworkSelection(BuildContext context) async {
|
||||
if (!_isNetworkSelectable) return;
|
||||
|
||||
final evmNetworks =
|
||||
availableWalletTypes.where((walletType) => isEVMCompatibleChain(walletType)).toList();
|
||||
final selectedIndex = evmNetworks.indexOf(selectedNetwork ?? WalletType.ethereum);
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Picker(
|
||||
items: evmNetworks,
|
||||
displayItem: (WalletType network) => walletTypeToString(network),
|
||||
selectedAtIndex: selectedIndex,
|
||||
title: S.current.select_network,
|
||||
closeOnItemSelected: true,
|
||||
hasTitleSpacing: true,
|
||||
images: evmNetworks
|
||||
.map((network) => CakeImageWidget(
|
||||
imageUrl: getChainMonoImage(network),
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context).colorScheme.primary))
|
||||
.toList(),
|
||||
onItemSelected: (WalletType network) {
|
||||
setState(() {
|
||||
selectedNetwork = network;
|
||||
selectedToken = null;
|
||||
});
|
||||
_autoSelectToken();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showTokenSelection(BuildContext context) async {
|
||||
if (selectedNetwork == null || isLoadingTokens) return;
|
||||
|
||||
setState(() {
|
||||
isLoadingTokens = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final availableTokens = await TokenUtilities.getAvailableTokensForNetwork(
|
||||
selectedNetwork!,
|
||||
);
|
||||
|
||||
if (availableTokens.isEmpty) {
|
||||
setState(() {
|
||||
isLoadingTokens = false;
|
||||
});
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(S.current.error),
|
||||
content: const Text('No tokens available for this network'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(S.current.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedIndex = selectedToken != null ? availableTokens.indexOf(selectedToken!) : 0;
|
||||
|
||||
setState(() {
|
||||
isLoadingTokens = false;
|
||||
});
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CurrencyPicker(
|
||||
selectedAtIndex: selectedIndex >= 0 ? selectedIndex : 0,
|
||||
items: availableTokens.cast<Currency>(),
|
||||
hintText: S.current.add_token,
|
||||
onItemSelected: (Currency currency) {
|
||||
setState(() {
|
||||
selectedToken = currency as CryptoCurrency;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
printV('Error showing token selection: $e');
|
||||
setState(() {
|
||||
isLoadingTokens = false;
|
||||
tokenLoadError = 'Failed to load tokens';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleNext(BuildContext context) async {
|
||||
if (selectedNetwork == null || selectedToken == null) return;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final compatibleWallets = await widget.paymentViewModel.getWalletsByType(selectedNetwork!);
|
||||
|
||||
PaymentFlowResult newResult;
|
||||
|
||||
if (selectedNetwork == WalletType.solana) {
|
||||
newResult = PaymentFlowResult.solanaTokenSelection(
|
||||
AddressDetectionResult(
|
||||
address: widget.paymentRequest.address,
|
||||
detectedWalletType: WalletType.solana,
|
||||
detectedCurrency: selectedToken!,
|
||||
isValid: true,
|
||||
amount: widget.paymentRequest.resolveTokenAmount(selectedToken!),
|
||||
note: widget.paymentRequest.note,
|
||||
scheme: widget.paymentRequest.scheme,
|
||||
pjUri: widget.paymentRequest.pjUri,
|
||||
callbackUrl: widget.paymentRequest.callbackUrl,
|
||||
callbackMessage: widget.paymentRequest.callbackMessage,
|
||||
),
|
||||
compatibleWallets: compatibleWallets,
|
||||
wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
|
||||
);
|
||||
} else if (selectedNetwork == WalletType.tron) {
|
||||
newResult = PaymentFlowResult.tronTokenSelection(
|
||||
AddressDetectionResult(
|
||||
address: widget.paymentRequest.address,
|
||||
detectedWalletType: WalletType.tron,
|
||||
detectedCurrency: selectedToken!,
|
||||
isValid: true,
|
||||
amount: widget.paymentRequest.resolveTokenAmount(selectedToken!),
|
||||
note: widget.paymentRequest.note,
|
||||
scheme: widget.paymentRequest.scheme,
|
||||
pjUri: widget.paymentRequest.pjUri,
|
||||
callbackUrl: widget.paymentRequest.callbackUrl,
|
||||
callbackMessage: widget.paymentRequest.callbackMessage,
|
||||
),
|
||||
compatibleWallets: compatibleWallets,
|
||||
wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
|
||||
);
|
||||
} else {
|
||||
newResult = PaymentFlowResult.evmNetworkSelection(
|
||||
AddressDetectionResult(
|
||||
address: widget.paymentRequest.address,
|
||||
detectedWalletType: selectedNetwork!,
|
||||
detectedCurrency: selectedToken!,
|
||||
isValid: true,
|
||||
amount: widget.paymentRequest.resolveTokenAmount(selectedToken!),
|
||||
note: widget.paymentRequest.note,
|
||||
scheme: widget.paymentRequest.scheme,
|
||||
pjUri: widget.paymentRequest.pjUri,
|
||||
callbackUrl: widget.paymentRequest.callbackUrl,
|
||||
callbackMessage: widget.paymentRequest.callbackMessage,
|
||||
),
|
||||
compatibleWallets: compatibleWallets,
|
||||
wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
|
||||
);
|
||||
}
|
||||
|
||||
widget.paymentViewModel.detectedWalletType = selectedNetwork!;
|
||||
widget.onNext(newResult);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenSelectionTileWidget extends StatelessWidget {
|
||||
const TokenSelectionTileWidget({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.enabled,
|
||||
required this.onTap,
|
||||
this.imagePath,
|
||||
this.color,
|
||||
});
|
||||
|
||||
final String value;
|
||||
final bool enabled;
|
||||
final VoidCallback? onTap;
|
||||
final String? imagePath;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: enabled ? onTap : null,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Opacity(
|
||||
opacity: enabled ? 1.0 : 0.6,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (imagePath != null) ...[
|
||||
CakeImageWidget(
|
||||
imageUrl: imagePath!,
|
||||
color: color,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (enabled)
|
||||
Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: 24,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user