mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
Cw 78 ethereum (#862)
* Add initial flow for ethereum * Add initial create Eth wallet flow * Complete Ethereum wallet creation flow * Fix web3dart versioning issue * Add primary receive address extracted from private key * Implement open wallet functionality * Implement restore wallet from seed functionality * Fixate web3dart version as higher versions cause some issues * Add Initial Transaction priorities for eth Add estimated gas price * Rename priority value to tip * Re-order wallet types * Change ethereum node Fix connection issues * Fix estimating gas for priority * Add case for ethereum to fetch it's seeds * Add case for ethereum to request node * Fix Exchange screen initial pairs * Add initial send transaction flow * Add missing configure for ethereum class * Add Eth address initial setup * Fix Private key for Ethereum wallets * Change sign/send transaction flow * - Fix Conflicts with main - Remove unused function from Haven configure.dart * Add build command for ethereum package * Add missing Node list file to pubspec * - Fix balance display - Fix parsing of Ethereum amount - Add more Ethereum Nodes * - Fix extracting Ethereum Private key from seeds - Integrate signing/sending transaction with the send view model * - Update and Fix Conflicts with main * Add Balances for ERC20 tokens * Fix conflicts with main * Add erc20 abi json * Add send erc20 tokens initial function * add missing getHeightByDate in Haven * Allow contacts and wallets from the same tag * Add Shiba Inu icon * Add send ERC-20 tokens initial flow * Add missing import in generated file * Add initial approach for transaction sending for ERC-20 tokens * Refactor signing/sending transactions * Add initial flow for transactions subscription * Refactor signing/sending transactions * Add home settings icon * Fix conflicts with main * Initial flow for home settings * Add logic flow for adding erc20 tokens * Fix initial UI * Finalize UI for Tokens * Integrate UI with Ethereum flow * Add "Enable/Disable" feature for ERC20 tokens * Add initial Erc20 tokens * Add Sorting and Pin Native Token features * Fix price sorting * Sort tokens list as well when Sort criteria changes * - Improve sorting balances flow - Add initial add token from search bar flow * Fix Accounts Popup UI * Fix Pin native token * Fix Enabling/Disabling tokens Fix sorting by fiat once app is opened Improve token availability mechanism * Fix deleting token Fix renaming tokens * Fix issue with search * Add more tokens * - Fix scroll issue - Add ERC20 tokens placeholder image in picker * - Separate and organize default erc20 tokens - Fix scrolling - Add token placeholder images in picker - Sort disabled tokens alphabetically * Change BNB token initial availability * Fix Conflicts with main * Fix Conflicts with main * Add Verse ERC20 token to the initial tokens list * Add rename wallet to Ethereum * Integrate EtherScan API for fetching address transactions Generate Ethereum specific secrets in Ethereum package * Adjust transactions fiat price for ERC20 tokens * Free Up GitHub Actions Ubuntu Runner Disk Space * Free Up GitHub Actions Ubuntu Runner Disk space (trial 2) * Fix Transaction Fee display * Save transaction history * Enhance loading time for erc20 tokens transactions * Minor Fixes and Enhancements * Fix sending erc20 fix block explorer issue * Fix int overflow * Fix transaction amount conversions * Minor: `slow` -> `Slow` * Update build guide * Fix fetching fiat rate taking a lot of time by only fetching enabled tokens only and making the API calls in parallel not sequential * Update transactions on a periodic basis * For fee, use ETH spot price, not ERC-20 spot price * Add Etherscan History privacy option to enable/disable Etherscan API * Show estimated fee amounts in the send screen * fix send fiat fields parsing issue * Fix transactions estimated fee less than actual fee * handle balance sorting when balance is disabled Handle empty transactions list * Fix Delete Ethereum wallet Fix balance < 0.01 * Fix Decimal place for Ethereum amount Fix sending amount issue * Change words count * Remove balance hint and Full balance row from Ethereum wallets * support changing the asset type in send templates * Fix Templates for ERC tokens issues * Fix conflicts in send templates * Disable batch sending in Ethereum * Fix Fee calculation with different priorities * Fix Conflicts with main * Add offline error to ignored exceptions --------- Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
This commit is contained in:
co-authored by
Justin Ehrenhofer
parent
4120394121
commit
3ce4000dcf
@@ -30,6 +30,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
|
||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||
@@ -136,6 +137,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
return litecoinIcon;
|
||||
case WalletType.haven:
|
||||
return havenIcon;
|
||||
case WalletType.ethereum:
|
||||
return ethereumIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/address_text_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/checkbox_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class EditTokenPage extends BasePage {
|
||||
EditTokenPage({
|
||||
Key? key,
|
||||
required this.homeSettingsViewModel,
|
||||
this.erc20token,
|
||||
this.initialContractAddress,
|
||||
}) : assert(erc20token == null || initialContractAddress == null);
|
||||
|
||||
final HomeSettingsViewModel homeSettingsViewModel;
|
||||
final Erc20Token? erc20token;
|
||||
final String? initialContractAddress;
|
||||
|
||||
@override
|
||||
String? get title => S.current.edit_token;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return EditTokenPageBody(
|
||||
homeSettingsViewModel: homeSettingsViewModel,
|
||||
erc20token: erc20token,
|
||||
initialContractAddress: initialContractAddress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EditTokenPageBody extends StatefulWidget {
|
||||
const EditTokenPageBody({
|
||||
Key? key,
|
||||
required this.homeSettingsViewModel,
|
||||
this.erc20token,
|
||||
this.initialContractAddress,
|
||||
}) : super(key: key);
|
||||
|
||||
final HomeSettingsViewModel homeSettingsViewModel;
|
||||
final Erc20Token? erc20token;
|
||||
final String? initialContractAddress;
|
||||
|
||||
@override
|
||||
State<EditTokenPageBody> createState() => _EditTokenPageBodyState();
|
||||
}
|
||||
|
||||
class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
final TextEditingController _contractAddressController = TextEditingController();
|
||||
final TextEditingController _tokenNameController = TextEditingController();
|
||||
final TextEditingController _tokenSymbolController = TextEditingController();
|
||||
final TextEditingController _tokenDecimalController = TextEditingController();
|
||||
|
||||
final FocusNode _contractAddressFocusNode = FocusNode();
|
||||
final FocusNode _tokenNameFocusNode = FocusNode();
|
||||
final FocusNode _tokenSymbolFocusNode = FocusNode();
|
||||
final FocusNode _tokenDecimalFocusNode = FocusNode();
|
||||
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
bool _showDisclaimer = false;
|
||||
bool _disclaimerChecked = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.erc20token != null) {
|
||||
_contractAddressController.text = widget.erc20token!.contractAddress;
|
||||
_tokenNameController.text = widget.erc20token!.name;
|
||||
_tokenSymbolController.text = widget.erc20token!.symbol;
|
||||
_tokenDecimalController.text = widget.erc20token!.decimal.toString();
|
||||
}
|
||||
|
||||
if (widget.initialContractAddress != null) {
|
||||
_contractAddressController.text = widget.initialContractAddress!;
|
||||
_getTokenInfo();
|
||||
}
|
||||
|
||||
_contractAddressFocusNode.addListener(() {
|
||||
if (!_contractAddressFocusNode.hasFocus) {
|
||||
_getTokenInfo();
|
||||
}
|
||||
|
||||
final contractAddress = _contractAddressController.text;
|
||||
if (contractAddress.isNotEmpty && contractAddress != widget.erc20token?.contractAddress) {
|
||||
setState(() {
|
||||
_showDisclaimer = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 25),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 28),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset('assets/images/restore_keys.png'),
|
||||
const SizedBox(width: 24),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).warning,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
S.of(context).add_token_warning,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).primaryTextTheme.labelSmall!.color!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 50),
|
||||
_tokenForm(),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
if (_showDisclaimer) ...[
|
||||
CheckboxWidget(
|
||||
value: _disclaimerChecked,
|
||||
caption: S.of(context).add_token_disclaimer_check,
|
||||
onChanged: (value) {
|
||||
_disclaimerChecked = value;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
if (widget.erc20token != null) {
|
||||
await widget.homeSettingsViewModel.deleteErc20Token(widget.erc20token!);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
text: widget.erc20token != null ? S.of(context).delete : S.of(context).cancel,
|
||||
color: Colors.red,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState!.validate() &&
|
||||
(!_showDisclaimer || _disclaimerChecked)) {
|
||||
await widget.homeSettingsViewModel.addErc20Token(Erc20Token(
|
||||
name: _tokenNameController.text,
|
||||
symbol: _tokenSymbolController.text,
|
||||
contractAddress: _contractAddressController.text,
|
||||
decimal: int.parse(_tokenDecimalController.text),
|
||||
));
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
text: S.of(context).save,
|
||||
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _getTokenInfo() async {
|
||||
if (_contractAddressController.text.isNotEmpty) {
|
||||
final token =
|
||||
await widget.homeSettingsViewModel.getErc20Token(_contractAddressController.text);
|
||||
|
||||
if (token != null) {
|
||||
if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
|
||||
if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.symbol;
|
||||
if (_tokenDecimalController.text.isEmpty)
|
||||
_tokenDecimalController.text = token.decimal.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pasteText() async {
|
||||
final value = await Clipboard.getData('text/plain');
|
||||
|
||||
if (value?.text?.isNotEmpty ?? false) {
|
||||
_contractAddressController.text = value!.text!;
|
||||
|
||||
_getTokenInfo();
|
||||
setState(() {
|
||||
_showDisclaimer = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _tokenForm() {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
AddressTextField(
|
||||
controller: _contractAddressController,
|
||||
focusNode: _contractAddressFocusNode,
|
||||
placeholder: S.of(context).token_contract_address,
|
||||
options: [AddressTextFieldOption.paste],
|
||||
buttonColor: Theme.of(context).hintColor,
|
||||
validator: AddressValidator(type: widget.homeSettingsViewModel.nativeToken),
|
||||
onPushPasteButton: (_) {
|
||||
_pasteText();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
BaseTextFormField(
|
||||
controller: _tokenNameController,
|
||||
focusNode: _tokenNameFocusNode,
|
||||
onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenSymbolFocusNode),
|
||||
textInputAction: TextInputAction.next,
|
||||
hintText: S.of(context).token_name,
|
||||
validator: (text) {
|
||||
if (text?.isNotEmpty ?? false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.of(context).field_required;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
BaseTextFormField(
|
||||
controller: _tokenSymbolController,
|
||||
focusNode: _tokenSymbolFocusNode,
|
||||
onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenDecimalFocusNode),
|
||||
textInputAction: TextInputAction.next,
|
||||
hintText: S.of(context).token_symbol,
|
||||
validator: (text) {
|
||||
if (text?.isNotEmpty ?? false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.of(context).field_required;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
BaseTextFormField(
|
||||
controller: _tokenDecimalController,
|
||||
focusNode: _tokenDecimalFocusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
hintText: S.of(context).token_decimal,
|
||||
validator: (text) {
|
||||
if (text?.isEmpty ?? true) {
|
||||
return S.of(context).field_required;
|
||||
}
|
||||
if (int.tryParse(text!) == null) {
|
||||
return S.of(context).invalid_input;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/entities/sort_balance_types.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/screens/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class HomeSettingsPage extends BasePage {
|
||||
HomeSettingsPage(this._homeSettingsViewModel);
|
||||
|
||||
final HomeSettingsViewModel _homeSettingsViewModel;
|
||||
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
String? get title => S.current.home_screen_settings;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => SettingsPickerCell<SortBalanceBy>(
|
||||
title: S.current.sort_by,
|
||||
items: SortBalanceBy.values,
|
||||
selectedItem: _homeSettingsViewModel.sortBalanceBy,
|
||||
onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
|
||||
Observer(
|
||||
builder: (_) => SettingsSwitcherCell(
|
||||
title: S.of(context).pin_at_top(_homeSettingsViewModel.nativeToken.title),
|
||||
value: _homeSettingsViewModel.pinNativeToken,
|
||||
onValueChange: (_, bool value) {
|
||||
_homeSettingsViewModel.setPinNativeToken(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 16),
|
||||
child: TextFormField(
|
||||
controller: _searchController,
|
||||
style: TextStyle(color: Theme.of(context).primaryTextTheme.titleLarge!.color!),
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).search_add_token,
|
||||
prefixIcon: Image.asset("assets/images/search_icon.png"),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).accentTextTheme.displaySmall!.color!,
|
||||
alignLabelWithHint: false,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
borderSide: const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
borderSide: const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
),
|
||||
onChanged: (String text) => _homeSettingsViewModel.changeSearchText(text),
|
||||
),
|
||||
),
|
||||
),
|
||||
RawMaterialButton(
|
||||
onPressed: () async {
|
||||
Navigator.pushNamed(context, Routes.editToken, arguments: {
|
||||
'homeSettingsViewModel': _homeSettingsViewModel,
|
||||
if (AddressValidator(type: _homeSettingsViewModel.nativeToken)
|
||||
.isValid(_searchController.text))
|
||||
'contractAddress': _searchController.text,
|
||||
});
|
||||
},
|
||||
elevation: 0,
|
||||
fillColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||
size: 22.0,
|
||||
),
|
||||
padding: EdgeInsets.all(12),
|
||||
shape: CircleBorder(),
|
||||
splashColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16, left: 16, right: 16),
|
||||
child: Observer(
|
||||
builder: (_) => ListView.builder(
|
||||
itemCount: _homeSettingsViewModel.tokens.length,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 16),
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final token = _homeSettingsViewModel.tokens.elementAt(index);
|
||||
|
||||
return SettingsSwitcherCell(
|
||||
title: "${token.name} "
|
||||
"(${token.symbol})",
|
||||
value: token.enabled,
|
||||
onValueChange: (_, bool value) {
|
||||
_homeSettingsViewModel.changeTokenAvailability(token, value);
|
||||
},
|
||||
onTap: (_) {
|
||||
Navigator.pushNamed(context, Routes.editToken, arguments: {
|
||||
'homeSettingsViewModel': _homeSettingsViewModel,
|
||||
'token': token,
|
||||
});
|
||||
},
|
||||
leading: token.iconPath != null
|
||||
? Container(
|
||||
child: Image.asset(
|
||||
token.iconPath!,
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
token.symbol.substring(0, min(token.symbol.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,15 +27,15 @@ class AddressPage extends BasePage {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}) : _cryptoAmountFocus = FocusNode(),
|
||||
_formKey = GlobalKey<FormState>(),
|
||||
_amountController = TextEditingController() {
|
||||
_amountController.addListener(() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
addressListViewModel.changeAmount(
|
||||
_amountController.text,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,15 +63,11 @@ class AddressPage extends BasePage {
|
||||
Widget? leading(BuildContext context) {
|
||||
final _backButton = Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
size: 16,
|
||||
);
|
||||
final _closeButton = currentTheme.type == ThemeType.dark
|
||||
? closeButtonImageDarkTheme
|
||||
: closeButtonImage;
|
||||
final _closeButton =
|
||||
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
|
||||
|
||||
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
||||
|
||||
@@ -82,13 +78,10 @@ class AddressPage extends BasePage {
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: Semantics(
|
||||
label: !isMobileView
|
||||
? S.of(context).close
|
||||
: S.of(context).seed_alert_back,
|
||||
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateColor.resolveWith(
|
||||
(states) => Colors.transparent),
|
||||
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
||||
),
|
||||
onPressed: () => onClose(context),
|
||||
child: !isMobileView ? _closeButton : _backButton,
|
||||
@@ -100,8 +93,7 @@ class AddressPage extends BasePage {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) =>
|
||||
PresentReceiveOptionPicker(
|
||||
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
|
||||
receiveOptionViewModel: receiveOptionViewModel,
|
||||
hasWhiteBackground: currentTheme.type == ThemeType.light,
|
||||
);
|
||||
@@ -136,10 +128,7 @@ class AddressPage extends BasePage {
|
||||
icon: Icon(
|
||||
Icons.share,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -180,10 +169,7 @@ class AddressPage extends BasePage {
|
||||
tapOutsideToDismiss: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyLarge!
|
||||
.backgroundColor!,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
@@ -205,62 +191,54 @@ class AddressPage extends BasePage {
|
||||
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
||||
ThemeType.light))),
|
||||
Observer(builder: (_) {
|
||||
return addressListViewModel.hasAddressList
|
||||
? GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.only(left: 24, right: 12),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.titleMedium!
|
||||
.color!,
|
||||
width: 1),
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.titleLarge!
|
||||
.backgroundColor!),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => Text(
|
||||
addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts_subaddresses
|
||||
: S.of(context).addresses,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!),
|
||||
)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!));
|
||||
if (addressListViewModel.hasAddressList) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.only(left: 24, right: 12),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).textTheme.titleMedium!.color!, width: 1),
|
||||
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => Text(
|
||||
addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts_subaddresses
|
||||
: S.of(context).addresses,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme.displayMedium!
|
||||
.backgroundColor!),
|
||||
)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (addressListViewModel.showElectrumAddressDisclaimer) {
|
||||
return Text(S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!));
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.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';
|
||||
@@ -20,51 +20,78 @@ class BalancePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(height: 56),
|
||||
Container(
|
||||
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 56),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 24, bottom: 16),
|
||||
child: Observer(builder: (_) {
|
||||
return Text(dashboardViewModel.balanceViewModel.asset,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center);
|
||||
})),
|
||||
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: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
Observer(builder: (_) {
|
||||
return ListView.separated(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
|
||||
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
|
||||
itemBuilder: (__, index) {
|
||||
final balance =
|
||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||
return buildBalanceRow(context,
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
dashboardViewModel.balanceViewModel.asset,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (dashboardViewModel.balanceViewModel.isHomeScreenSettingsEnabled)
|
||||
InkWell(
|
||||
onTap: () => Navigator.pushNamed(context, Routes.homeSettings,
|
||||
arguments: dashboardViewModel.balanceViewModel),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image.asset(
|
||||
'assets/images/home_screen_settings_icon.png',
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
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: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return ListView.separated(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
|
||||
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
|
||||
itemBuilder: (__, index) {
|
||||
final balance =
|
||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||
return buildBalanceRow(
|
||||
context,
|
||||
availableBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||
availableBalance: balance.availableBalance,
|
||||
@@ -75,45 +102,57 @@ class BalancePage extends StatelessWidget {
|
||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||
frozenBalance: balance.frozenBalance,
|
||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||
currency: balance.formattedAssetTitle);
|
||||
});
|
||||
})
|
||||
])));
|
||||
currency: balance.formattedAssetTitle,
|
||||
hasAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBalanceRow(BuildContext context,
|
||||
{required String availableBalanceLabel,
|
||||
required String availableBalance,
|
||||
required String availableFiatBalance,
|
||||
required String additionalBalanceLabel,
|
||||
required String additionalBalance,
|
||||
required String additionalFiatBalance,
|
||||
required String frozenBalance,
|
||||
required String frozenFiatBalance,
|
||||
required String currency}) {
|
||||
Widget buildBalanceRow(
|
||||
BuildContext context, {
|
||||
required String availableBalanceLabel,
|
||||
required String availableBalance,
|
||||
required String availableFiatBalance,
|
||||
required String additionalBalanceLabel,
|
||||
required String additionalBalance,
|
||||
required String additionalFiatBalance,
|
||||
required String frozenBalance,
|
||||
required String frozenFiatBalance,
|
||||
required String currency,
|
||||
required bool hasAdditionalBalance,
|
||||
}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
border: Border.all(
|
||||
color: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
color: Theme.of(context).textTheme!.titleLarge!.backgroundColor!),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
border: Border.all(
|
||||
color: settingsStore.currentTheme.type == ThemeType.bright
|
||||
? Color.fromRGBO(255, 255, 255, 0.2)
|
||||
: Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _showBalanceDescription(context),
|
||||
onTap: hasAdditionalBalance ? () => _showBalanceDescription(context) : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -129,19 +168,19 @@ class BalancePage extends StatelessWidget {
|
||||
.displaySmall!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!),
|
||||
)
|
||||
if (hasAdditionalBalance)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!),
|
||||
),
|
||||
],
|
||||
),SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
AutoSizeText(availableBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
@@ -154,9 +193,7 @@ class BalancePage extends StatelessWidget {
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.start),
|
||||
SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Text('${availableFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
@@ -168,7 +205,6 @@ class BalancePage extends StatelessWidget {
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -177,97 +213,99 @@ class BalancePage extends StatelessWidget {
|
||||
fontSize: 28,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
|
||||
height: 1)),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 26),
|
||||
if (frozenBalance.isNotEmpty)
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(S.current.frozen_balance,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 26),
|
||||
Text(
|
||||
S.current.frozen_balance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(frozenBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
),
|
||||
SizedBox(height: 24)
|
||||
]),
|
||||
Text('${additionalBalanceLabel}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displaySmall!
|
||||
.backgroundColor!,
|
||||
height: 1)),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(additionalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.displayMedium!
|
||||
.backgroundColor!,
|
||||
height: 1),
|
||||
)
|
||||
])),
|
||||
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(
|
||||
frozenBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (hasAdditionalBalance)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
Text(
|
||||
'${additionalBalanceLabel}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(
|
||||
additionalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,17 +19,18 @@ class MenuWidget extends StatefulWidget {
|
||||
|
||||
class MenuWidgetState extends State<MenuWidget> {
|
||||
MenuWidgetState()
|
||||
: this.menuWidth = 0,
|
||||
this.screenWidth = 0,
|
||||
this.screenHeight = 0,
|
||||
this.headerHeight = 120,
|
||||
this.tileHeight = 60,
|
||||
this.fromTopEdge = 50,
|
||||
this.fromBottomEdge = 25,
|
||||
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
||||
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
||||
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
||||
this.havenIcon = Image.asset('assets/images/haven_menu.png');
|
||||
: this.menuWidth = 0,
|
||||
this.screenWidth = 0,
|
||||
this.screenHeight = 0,
|
||||
this.headerHeight = 120,
|
||||
this.tileHeight = 60,
|
||||
this.fromTopEdge = 50,
|
||||
this.fromBottomEdge = 25,
|
||||
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
||||
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
||||
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
||||
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
|
||||
this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
|
||||
|
||||
final largeScreen = 731;
|
||||
|
||||
@@ -46,6 +47,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
Image bitcoinIcon;
|
||||
Image litecoinIcon;
|
||||
Image havenIcon;
|
||||
Image ethereumIcon;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -85,16 +87,14 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
|
||||
moneroIcon = Image.asset('assets/images/monero_menu.png',
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.accentTextTheme
|
||||
.labelSmall!
|
||||
.decorationColor!);
|
||||
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.accentTextTheme
|
||||
.labelSmall!
|
||||
.decorationColor!);
|
||||
litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
|
||||
havenIcon = Image.asset('assets/images/haven_menu.png');
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
@@ -178,7 +178,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
index--;
|
||||
|
||||
final item = SettingActions.all[index];
|
||||
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
|
||||
return SettingActionButton(
|
||||
@@ -215,6 +215,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
return litecoinIcon;
|
||||
case WalletType.haven:
|
||||
return havenIcon;
|
||||
case WalletType.ethereum:
|
||||
return ethereumIcon;
|
||||
default:
|
||||
throw Exception('No icon for ${type.toString()}');
|
||||
}
|
||||
|
||||
@@ -511,17 +511,16 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
builder: (_) => CurrencyPicker(
|
||||
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
||||
items: widget.currencies,
|
||||
hintText: S.of(context).search_currency,
|
||||
isMoneroWallet: _isMoneroWallet,
|
||||
isConvertFrom: widget.hasRefundAddress,
|
||||
onItemSelected: (Currency item) =>
|
||||
widget.onCurrencySelected != null
|
||||
? widget.onCurrencySelected(item as CryptoCurrency)
|
||||
: null),
|
||||
context: context);
|
||||
context: context,
|
||||
builder: (_) => CurrencyPicker(
|
||||
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
||||
items: widget.currencies,
|
||||
hintText: S.of(context).search_currency,
|
||||
isMoneroWallet: _isMoneroWallet,
|
||||
isConvertFrom: widget.hasRefundAddress,
|
||||
onItemSelected: (Currency item) => widget.onCurrencySelected(item as CryptoCurrency),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAmountPopup(BuildContext context, PaymentRequest paymentRequest) {
|
||||
|
||||
@@ -11,14 +11,17 @@ import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewWalletTypePage extends BasePage {
|
||||
NewWalletTypePage({required this.onTypeSelected});
|
||||
NewWalletTypePage({required this.onTypeSelected, required this.isCreate});
|
||||
|
||||
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||
final bool isCreate;
|
||||
|
||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
|
||||
|
||||
@override
|
||||
String get title => S.current.wallet_list_restore_wallet;
|
||||
String get title =>
|
||||
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletTypeForm(
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class RestoreWalletOptionsPage extends BasePage {
|
||||
RestoreWalletOptionsPage(
|
||||
{required this.type,
|
||||
required this.onRestoreFromSeed,
|
||||
required this.onRestoreFromKeys});
|
||||
|
||||
final WalletType type;
|
||||
final Function(BuildContext context) onRestoreFromSeed;
|
||||
final Function(BuildContext context) onRestoreFromKeys;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_restore_wallet;
|
||||
|
||||
final imageSeed = Image.asset('assets/images/restore_seed.png');
|
||||
final imageKeys = Image.asset('assets/images/restore_keys.png');
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
RestoreButton(
|
||||
onPressed: () => onRestoreFromSeed(context),
|
||||
image: imageSeed,
|
||||
title: S.of(context).restore_title_from_seed,
|
||||
description: _fromSeedDescription(context)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () => onRestoreFromKeys(context),
|
||||
image: imageKeys,
|
||||
title: _fromKeyTitle(context),
|
||||
description: _fromKeyDescription(context)),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
String _fromSeedDescription(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_description_from_seed;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return S.of(context).restore_bitcoin_description_from_seed;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _fromKeyDescription(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_description_from_keys;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return S.of(context).restore_bitcoin_description_from_keys;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _fromKeyTitle(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_title_from_keys;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return S.of(context).restore_bitcoin_title_from_keys;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,7 @@ class PreSeedPage extends BasePage {
|
||||
PreSeedPage(this.type)
|
||||
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
|
||||
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
|
||||
wordsCount = type == WalletType.monero
|
||||
? 25
|
||||
: 24; // FIXME: Stupid fast implementation
|
||||
wordsCount = _wordsCount(type);
|
||||
|
||||
final Image imageDark;
|
||||
final Image imageLight;
|
||||
@@ -68,4 +66,15 @@ class PreSeedPage extends BasePage {
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
static int _wordsCount(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return 25;
|
||||
case WalletType.ethereum:
|
||||
return 12;
|
||||
default:
|
||||
return 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,115 +220,108 @@ class SendPage extends BasePage {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (sendViewModel.hasMultiRecipient)
|
||||
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;
|
||||
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(
|
||||
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 {
|
||||
if (template.additionalRecipients !=
|
||||
null) {
|
||||
sendViewModel.clearOutputs();
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
AddTemplateButton(
|
||||
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 {
|
||||
if (template.additionalRecipients?.isNotEmpty ?? false) {
|
||||
sendViewModel.clearOutputs();
|
||||
|
||||
template.additionalRecipients!
|
||||
.forEach((currentElement) async {
|
||||
int i = template
|
||||
.additionalRecipients!
|
||||
.indexOf(currentElement);
|
||||
for (int i = 0;i < template.additionalRecipients!.length;i++) {
|
||||
Output output;
|
||||
try {
|
||||
output = sendViewModel.outputs[i];
|
||||
} catch (e) {
|
||||
sendViewModel.addOutput();
|
||||
output = sendViewModel.outputs[i];
|
||||
}
|
||||
|
||||
Output output;
|
||||
try {
|
||||
output = sendViewModel.outputs[i];
|
||||
} catch (e) {
|
||||
sendViewModel.addOutput();
|
||||
output = sendViewModel.outputs[i];
|
||||
}
|
||||
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: currentElement);
|
||||
});
|
||||
} else {
|
||||
final output = _defineCurrentOutput();
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: template);
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: template.additionalRecipients![i],
|
||||
);
|
||||
}
|
||||
},
|
||||
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());
|
||||
},
|
||||
} else {
|
||||
final output = _defineCurrentOutput();
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: 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();
|
||||
sendViewModel
|
||||
.sendTemplateViewModel
|
||||
.removeTemplate(
|
||||
template: template);
|
||||
},
|
||||
actionLeftButton: () =>
|
||||
Navigator.of(dialogContext)
|
||||
.pop());
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -350,7 +343,7 @@ class SendPage extends BasePage {
|
||||
.displaySmall!
|
||||
.decorationColor!,
|
||||
))),
|
||||
if (sendViewModel.hasMultiRecipient)
|
||||
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
@@ -518,6 +511,7 @@ class SendPage extends BasePage {
|
||||
output.address = template.address;
|
||||
|
||||
if (template.isCurrencySelected) {
|
||||
sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency);
|
||||
output.setCryptoAmount(template.amount);
|
||||
} else {
|
||||
sendViewModel.setFiatCurrency(fiatFromTemplate);
|
||||
|
||||
@@ -67,8 +67,7 @@ class SendTemplatePage extends BasePage {
|
||||
controller: controller,
|
||||
itemCount: sendTemplateViewModel.recipients.length,
|
||||
itemBuilder: (_, index) {
|
||||
final template =
|
||||
sendTemplateViewModel.recipients[index];
|
||||
final template = sendTemplateViewModel.recipients[index];
|
||||
return SendTemplateCard(
|
||||
template: template,
|
||||
index: index,
|
||||
@@ -76,8 +75,7 @@ class SendTemplatePage extends BasePage {
|
||||
});
|
||||
})),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 10, left: 24, right: 24, bottom: 10),
|
||||
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
|
||||
child: Container(
|
||||
height: 10,
|
||||
child: Observer(
|
||||
@@ -107,55 +105,42 @@ class SendTemplatePage extends BasePage {
|
||||
),
|
||||
),
|
||||
])),
|
||||
bottomSectionPadding:
|
||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Column(children: [
|
||||
// if (sendViewModel.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)
|
||||
.accentTextTheme
|
||||
.displaySmall!
|
||||
.decorationColor!,
|
||||
isDottedBorder: true,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.displaySmall!
|
||||
.decorationColor!)),
|
||||
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).accentTextTheme.displaySmall!.decorationColor!,
|
||||
isDottedBorder: true,
|
||||
borderColor:
|
||||
Theme.of(context).primaryTextTheme.displaySmall!.decorationColor!)),
|
||||
PrimaryButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState != null &&
|
||||
_formKey.currentState!.validate()) {
|
||||
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
|
||||
final mainTemplate = sendTemplateViewModel.recipients[0];
|
||||
print(sendTemplateViewModel.recipients.map((element) =>
|
||||
element.toTemplate(
|
||||
cryptoCurrency:
|
||||
sendTemplateViewModel.cryptoCurrency.title,
|
||||
fiatCurrency:
|
||||
sendTemplateViewModel.fiatCurrency)));
|
||||
final additionalRecipients = sendTemplateViewModel.recipients
|
||||
.map((element) => element.toTemplate(
|
||||
cryptoCurrency: element.selectedCurrency.title,
|
||||
fiatCurrency: sendTemplateViewModel.fiatCurrency))
|
||||
.toList();
|
||||
|
||||
sendTemplateViewModel.addTemplate(
|
||||
isCurrencySelected: mainTemplate.isCurrencySelected,
|
||||
name: mainTemplate.name,
|
||||
address: mainTemplate.address,
|
||||
cryptoCurrency: mainTemplate.selectedCurrency.title,
|
||||
amount: mainTemplate.output.cryptoAmount,
|
||||
amountFiat: mainTemplate.output.fiatAmount,
|
||||
additionalRecipients: sendTemplateViewModel.recipients
|
||||
.map((element) => element.toTemplate(
|
||||
cryptoCurrency: sendTemplateViewModel
|
||||
.cryptoCurrency.title,
|
||||
fiatCurrency:
|
||||
sendTemplateViewModel.fiatCurrency))
|
||||
.toList());
|
||||
additionalRecipients: additionalRecipients);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,29 +4,53 @@ class PrefixCurrencyIcon extends StatelessWidget {
|
||||
PrefixCurrencyIcon({
|
||||
required this.isSelected,
|
||||
required this.title,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final bool isSelected;
|
||||
final String title;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
|
||||
child: Column(children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
color: isSelected ? Colors.green : Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
color: isSelected ? Colors.green : Colors.transparent,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (onTap != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Text(title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
)
|
||||
]));
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
@@ -32,18 +35,14 @@ class SendCard extends StatefulWidget {
|
||||
|
||||
@override
|
||||
SendCardState createState() => SendCardState(
|
||||
output: output,
|
||||
sendViewModel: sendViewModel,
|
||||
initialPaymentRequest: initialPaymentRequest,
|
||||
);
|
||||
output: output,
|
||||
sendViewModel: sendViewModel,
|
||||
initialPaymentRequest: initialPaymentRequest,
|
||||
);
|
||||
}
|
||||
|
||||
class SendCardState extends State<SendCard>
|
||||
with AutomaticKeepAliveClientMixin<SendCard> {
|
||||
SendCardState({
|
||||
required this.output,
|
||||
required this.sendViewModel,
|
||||
this.initialPaymentRequest})
|
||||
class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<SendCard> {
|
||||
SendCardState({required this.output, required this.sendViewModel, this.initialPaymentRequest})
|
||||
: addressController = TextEditingController(),
|
||||
cryptoAmountController = TextEditingController(),
|
||||
fiatAmountController = TextEditingController(),
|
||||
@@ -100,40 +99,41 @@ class SendCardState extends State<SendCard>
|
||||
return Stack(
|
||||
children: [
|
||||
KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.bodyLarge!
|
||||
.backgroundColor!,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
KeyboardActionsItem(
|
||||
focusNode: fiatAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
]),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Colors.transparent,
|
||||
)),
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
KeyboardActionsItem(
|
||||
focusNode: fiatAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: ResponsiveLayoutUtil.instance.isMobile ? BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.titleMedium!
|
||||
.decorationColor!,
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||
) : null,
|
||||
decoration: ResponsiveLayoutUtil.instance.isMobile
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
24,
|
||||
@@ -142,7 +142,8 @@ class SendCardState extends State<SendCard>
|
||||
ResponsiveLayoutUtil.instance.isMobile ? 32 : 0,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Observer(builder: (_) => Column(
|
||||
child: Observer(
|
||||
builder: (_) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Observer(builder: (_) {
|
||||
@@ -164,25 +165,15 @@ class SendCardState extends State<SendCard>
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
buttonColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||
onPushPasteButton: (context) async {
|
||||
output.resetParsedAddress();
|
||||
await output.fetchParsedAddress(context);
|
||||
@@ -197,170 +188,192 @@ class SendCardState extends State<SendCard>
|
||||
selectedCurrency: sendViewModel.currency,
|
||||
);
|
||||
}),
|
||||
if (output.isParsedAddress) Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
controller: extractedAddressController,
|
||||
readOnly: true,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
validator: sendViewModel.addressValidator
|
||||
)
|
||||
),
|
||||
if (output.isParsedAddress)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
controller: extractedAddressController,
|
||||
readOnly: true,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
validator: sendViewModel.addressValidator)),
|
||||
Observer(
|
||||
builder: (_) => Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
sendViewModel.selectedCryptoCurrency.tag != null ? Padding(
|
||||
padding: const EdgeInsets.fromLTRB(3.0,0,3.0,0),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(6))),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text( sendViewModel.selectedCryptoCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!)),
|
||||
builder: (_) => Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
sendViewModel.hasMultipleTokens
|
||||
? Container(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
height: 32,
|
||||
child: InkWell(
|
||||
onTap: () => _presentPicker(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
) : Container(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(':',
|
||||
)
|
||||
: Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
focusNode: cryptoAmountFocus,
|
||||
controller: cryptoAmountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
suffixIcon: SizedBox(
|
||||
width: prefixIconWidth,
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Colors.transparent,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
),
|
||||
sendViewModel.selectedCryptoCurrency.tag != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
)),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
sendViewModel.selectedCryptoCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
focusNode: cryptoAmountFocus,
|
||||
controller: cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
suffixIcon: SizedBox(
|
||||
width: prefixIconWidth,
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Colors.transparent,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: output.sendAll
|
||||
? sendViewModel.allAmountValidator
|
||||
: sendViewModel.amountValidator,
|
||||
),
|
||||
if (!sendViewModel.isBatchSending)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: prefixIconWidth,
|
||||
height: prefixIconHeight,
|
||||
child: InkWell(
|
||||
onTap: () async => output.setSendAll(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: output.sendAll
|
||||
? sendViewModel.allAmountValidator
|
||||
: sendViewModel
|
||||
.amountValidator),
|
||||
if (!sendViewModel.isBatchSending) Positioned(
|
||||
top: 2,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: prefixIconWidth,
|
||||
height: prefixIconHeight,
|
||||
child: InkWell(
|
||||
onTap: () async =>
|
||||
output.setSendAll(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(
|
||||
Radius.circular(6))),
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).all,
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!))),
|
||||
))))]),
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).all,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
)),
|
||||
Divider(height: 1,color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||
Observer(
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.of(context).available_balance +
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
)),
|
||||
Text(
|
||||
sendViewModel.balance,
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.of(context).available_balance + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -368,10 +381,22 @@ class SendCardState extends State<SendCard>
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
sendViewModel.balance,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!sendViewModel.isFiatDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
@@ -379,171 +404,155 @@ class SendCardState extends State<SendCard>
|
||||
focusNode: fiatAmountFocus,
|
||||
controller: fiatAmountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
FilteringTextInputFormatter.deny(
|
||||
RegExp('[\\-|\\ ]'),
|
||||
)
|
||||
],
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.only(top: 9),
|
||||
child:
|
||||
Text(sendViewModel.fiat.title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
child: Text(
|
||||
sendViewModel.fiat.title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
controller: noteController,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
hintText: S.of(context).note_optional,
|
||||
placeholderTextStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: () =>
|
||||
_setTransactionPriority(context),
|
||||
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: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w500,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white)),
|
||||
Container(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
output
|
||||
.estimatedFee
|
||||
.toString() +
|
||||
' ' +
|
||||
sendViewModel
|
||||
.selectedCryptoCurrency.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color:
|
||||
Colors.white)),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: 5),
|
||||
child: sendViewModel.isFiatDisabled
|
||||
? const SizedBox(height: 14)
|
||||
: Text(output
|
||||
.estimatedFeeFiatAmount
|
||||
+ ' ' +
|
||||
sendViewModel
|
||||
.fiat.title,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
color: Theme
|
||||
.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!))
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: () => _setTransactionPriority(context),
|
||||
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: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
output.estimatedFee.toString() +
|
||||
' ' +
|
||||
sendViewModel.selectedCryptoCurrency.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 2,
|
||||
left: 5),
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: sendViewModel.isFiatDisabled
|
||||
? const SizedBox(height: 14)
|
||||
: Text(
|
||||
output.estimatedFeeFiatAmount +
|
||||
' ' +
|
||||
sendViewModel.fiat.title,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 2, left: 5),
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (sendViewModel.isElectrumWallet)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).coin_control,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (sendViewModel.isElectrumWallet) Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.unspentCoinsList),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).coin_control,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
))
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -552,10 +561,10 @@ class SendCardState extends State<SendCard>
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (output.address.isNotEmpty) {
|
||||
addressController.text = output.address;
|
||||
}
|
||||
@@ -664,16 +673,30 @@ class SendCardState extends State<SendCard>
|
||||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||
|
||||
await showPopUp<void>(
|
||||
builder: (_) => Picker(
|
||||
items: items,
|
||||
displayItem: sendViewModel.displayFeeRate,
|
||||
selectedAtIndex: selectedItem,
|
||||
title: S.of(context).please_select,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
onItemSelected: (TransactionPriority priority) =>
|
||||
sendViewModel.setTransactionPriority(priority),
|
||||
),
|
||||
context: context);
|
||||
context: context,
|
||||
builder: (_) => Picker(
|
||||
items: items,
|
||||
displayItem: sendViewModel.displayFeeRate,
|
||||
selectedAtIndex: selectedItem,
|
||||
title: S.of(context).please_select,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
onItemSelected: (TransactionPriority priority) =>
|
||||
sendViewModel.setTransactionPriority(priority),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => CurrencyPicker(
|
||||
selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency),
|
||||
items: sendViewModel.currencies,
|
||||
hintText: S.of(context).search_currency,
|
||||
onItemSelected: (Currency cur) =>
|
||||
sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.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:flutter/services.dart';
|
||||
@@ -35,161 +39,140 @@ class SendTemplateCard extends StatelessWidget {
|
||||
_setEffects(context);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||
child: Column(children: <Widget>[
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
||||
child: Column(children: <Widget>[
|
||||
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
if (index == 0)
|
||||
BaseTextFormField(
|
||||
controller: _nameController,
|
||||
hintText: sendTemplateViewModel.recipients.length > 1
|
||||
? S.of(context).template_name
|
||||
: S.of(context).send_name,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: sendTemplateViewModel.templateValidator),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: AddressTextField(
|
||||
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
||||
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();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineMedium!
|
||||
.color!,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
validator: sendTemplateViewModel.addressValidator)),
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: AddressTextField(
|
||||
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
||||
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();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
),
|
||||
validator: sendTemplateViewModel.addressValidator,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectCurrency();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
controller: _cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(
|
||||
RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: sendTemplateViewModel
|
||||
.cryptoCurrency.title,
|
||||
isSelected: template.isCurrencySelected)),
|
||||
hintText: '0.0000',
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: sendTemplateViewModel.amountValidator))),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectCurrency();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
controller: _cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: template.selectedCurrency.title,
|
||||
isSelected: template.isCurrencySelected,
|
||||
onTap: sendTemplateViewModel.walletCurrencies.length > 1
|
||||
? () => _presentPicker(context)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: sendTemplateViewModel.amountValidator,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectFiat();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _fiatAmountFocus,
|
||||
controller: _fiatAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(
|
||||
RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: sendTemplateViewModel.fiatCurrency,
|
||||
isSelected: template.isFiatSelected)),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14))))
|
||||
]))
|
||||
]));
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectFiat();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _fiatAmountFocus,
|
||||
controller: _fiatAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: sendTemplateViewModel.fiatCurrency,
|
||||
isSelected: template.isFiatSelected)),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
@@ -264,4 +247,16 @@ class SendTemplateCard extends StatelessWidget {
|
||||
|
||||
_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ class PrivacyPage extends BasePage {
|
||||
title: S.current.exchange,
|
||||
items: ExchangeApiMode.all,
|
||||
selectedItem: _privacySettingsViewModel.exchangeStatus,
|
||||
onItemSelected: (ExchangeApiMode mode) => _privacySettingsViewModel.setExchangeApiMode(mode),
|
||||
onItemSelected: (ExchangeApiMode mode) =>
|
||||
_privacySettingsViewModel.setExchangeApiMode(mode),
|
||||
),
|
||||
),
|
||||
SettingsSwitcherCell(
|
||||
@@ -68,6 +69,13 @@ class PrivacyPage extends BasePage {
|
||||
onValueChange: (BuildContext _, bool value) {
|
||||
_privacySettingsViewModel.setDisableSell(value);
|
||||
}),
|
||||
if (_privacySettingsViewModel.canUseEtherscan)
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.etherscan_history,
|
||||
value: _privacySettingsViewModel.useEtherscan,
|
||||
onValueChange: (BuildContext _, bool value) {
|
||||
_privacySettingsViewModel.setUseEtherscan(value);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -3,14 +3,23 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
||||
|
||||
class SettingsSwitcherCell extends StandardListRow {
|
||||
SettingsSwitcherCell(
|
||||
{required String title, required this.value, this.onValueChange})
|
||||
: super(title: title, isSelected: false);
|
||||
SettingsSwitcherCell({
|
||||
required String title,
|
||||
required this.value,
|
||||
this.onValueChange,
|
||||
Decoration? decoration,
|
||||
this.leading,
|
||||
void Function(BuildContext context)? onTap,
|
||||
}) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap);
|
||||
|
||||
final bool value;
|
||||
final void Function(BuildContext context, bool value)? onValueChange;
|
||||
final Widget? leading;
|
||||
|
||||
@override
|
||||
Widget buildTrailing(BuildContext context) => StandardSwitch(
|
||||
value: value, onTaped: () => onValueChange?.call(context, !value));
|
||||
Widget buildTrailing(BuildContext context) =>
|
||||
StandardSwitch(value: value, onTaped: () => onValueChange?.call(context, !value));
|
||||
|
||||
@override
|
||||
Widget? buildLeading(BuildContext context) => leading;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||
final scrollController = ScrollController();
|
||||
final double tileHeight = 60;
|
||||
Flushbar<void>? _progressBar;
|
||||
@@ -230,6 +231,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
return litecoinIcon;
|
||||
case WalletType.haven:
|
||||
return havenIcon;
|
||||
case WalletType.ethereum:
|
||||
return ethereumIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CheckboxWidget extends StatefulWidget {
|
||||
CheckboxWidget({
|
||||
required this.value,
|
||||
required this.caption,
|
||||
required this.onChanged});
|
||||
CheckboxWidget({required this.value, required this.caption, required this.onChanged});
|
||||
|
||||
final bool value;
|
||||
final String caption;
|
||||
@@ -26,55 +21,45 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
value = !value;
|
||||
onChanged(value);
|
||||
setState(() {});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
margin: EdgeInsets.only(right: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: value
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleMedium!
|
||||
.decorationColor!,
|
||||
borderRadius: BorderRadius.all(Radius.circular(2)),
|
||||
border: Border.all(
|
||||
color: value
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.labelSmall!
|
||||
.color!,
|
||||
width: 1)),
|
||||
child: value
|
||||
? Center(
|
||||
child: Icon(
|
||||
Icons.done,
|
||||
color: Colors.white,
|
||||
size: 14,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).primaryTextTheme.bodySmall!.color!,
|
||||
width: 1.0,
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8.0),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: value
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.blue,
|
||||
size: 20.0,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!,
|
||||
fontSize: 18,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: TextDecoration.none
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14.0,
|
||||
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -82,4 +67,4 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-44
@@ -1,5 +1,7 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -145,8 +147,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.color!,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -163,8 +164,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
),
|
||||
Divider(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.backgroundColor!,
|
||||
height: 1,
|
||||
),
|
||||
@@ -194,8 +194,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.titleLarge!
|
||||
.primaryTextTheme.titleLarge!
|
||||
.color!,
|
||||
),
|
||||
),
|
||||
@@ -217,8 +216,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
Widget itemsList() {
|
||||
return Container(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.backgroundColor!,
|
||||
child: widget.isGridView
|
||||
? GridView.builder(
|
||||
@@ -240,8 +238,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
separatorBuilder: (context, index) => widget.isSeparated
|
||||
? Divider(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.backgroundColor!,
|
||||
height: 1,
|
||||
)
|
||||
@@ -254,15 +251,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
|
||||
Widget buildItem(int index) {
|
||||
final item = filteredItems[index];
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
|
||||
final icon = item is Currency && item.iconPath != null
|
||||
? Image.asset(
|
||||
item.iconPath!,
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
)
|
||||
: null;
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
final icon = _getItemIcon(item);
|
||||
|
||||
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
||||
|
||||
@@ -274,8 +265,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
child: Container(
|
||||
height: 55,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.color!,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
@@ -298,8 +288,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.titleLarge!
|
||||
.primaryTextTheme.titleLarge!
|
||||
.color!,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
@@ -318,8 +307,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.color!),
|
||||
),
|
||||
),
|
||||
@@ -327,8 +315,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
@@ -345,15 +332,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
|
||||
Widget buildSelectedItem(int index) {
|
||||
final item = items[index];
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
|
||||
final icon = item is Currency && item.iconPath != null
|
||||
? Image.asset(
|
||||
item.iconPath!,
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
)
|
||||
: null;
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
final icon = _getItemIcon(item);
|
||||
|
||||
final image = images.isNotEmpty ? images[index] : icon;
|
||||
|
||||
@@ -364,8 +345,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
child: Container(
|
||||
height: 55,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.titleLarge!
|
||||
.accentTextTheme.titleLarge!
|
||||
.color!,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
@@ -388,8 +368,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.titleLarge!
|
||||
.primaryTextTheme.titleLarge!
|
||||
.color!,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
@@ -408,8 +387,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.color!),
|
||||
),
|
||||
),
|
||||
@@ -417,8 +395,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context)
|
||||
.textTheme!
|
||||
.bodyMedium!
|
||||
.textTheme.bodyMedium!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
@@ -429,12 +406,40 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
),
|
||||
Icon(Icons.check_circle,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyLarge!
|
||||
.accentTextTheme.bodyLarge!
|
||||
.color!),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _getItemIcon(Item item) {
|
||||
if (item is Currency) {
|
||||
if (item.iconPath != null) {
|
||||
return Image.asset(
|
||||
item.iconPath!,
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
item.name.substring(0, min(item.name.length, 2)).toUpperCase(),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class StandardListRow extends StatelessWidget {
|
||||
StandardListRow(
|
||||
{required this.title, required this.isSelected, this.onTap});
|
||||
{required this.title, required this.isSelected, this.onTap, this.decoration});
|
||||
|
||||
final String title;
|
||||
final bool isSelected;
|
||||
final void Function(BuildContext context)? onTap;
|
||||
final Decoration? decoration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -19,9 +20,11 @@ class StandardListRow extends StatelessWidget {
|
||||
return InkWell(
|
||||
onTap: () => onTap?.call(context),
|
||||
child: Container(
|
||||
color: _backgroundColor(context),
|
||||
height: 56,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
decoration: decoration ?? BoxDecoration(
|
||||
color: _backgroundColor(context),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
@@ -54,7 +57,7 @@ class StandardListRow extends StatelessWidget {
|
||||
|
||||
Color titleColor(BuildContext context) => isSelected
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context).primaryTextTheme!.titleLarge!.color!;
|
||||
: Theme.of(context).primaryTextTheme.titleLarge!.color!;
|
||||
|
||||
Color _backgroundColor(BuildContext context) {
|
||||
return Theme.of(context).colorScheme.background;
|
||||
@@ -89,7 +92,7 @@ class StandardListSeparator extends StatelessWidget {
|
||||
child: Container(
|
||||
height: height,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.primaryTextTheme
|
||||
.titleLarge
|
||||
?.backgroundColor));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user