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:
Omar Hatem
2023-08-04 20:01:49 +03:00
committed by GitHub
co-authored by Justin Ehrenhofer
parent 4120394121
commit 3ce4000dcf
137 changed files with 7164 additions and 1492 deletions
@@ -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 '';
}
}
}
+12 -3
View File
@@ -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;
}
}
}
+98 -104
View File
@@ -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);
+28 -43
View File
@@ -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,
)),
)
]));
],
),
),
);
}
}
+386 -363
View File
@@ -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),
),
);
}
}
+9 -1
View File
@@ -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;
}
+26 -41
View File
@@ -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
View File
@@ -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;
}
}
+7 -4
View File
@@ -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));
}