From 16dfb3a2d5c539ba6f9638a0034561353d7dfded Mon Sep 17 00:00:00 2001 From: Oleksandr Sobol Date: Tue, 12 May 2020 20:46:42 +0300 Subject: [PATCH 1/4] CWA-212 | applied new design to exchange page; reworked exchange card; added new properties to address_text_field, base_text_form_field and top_panel --- lib/src/screens/exchange/exchange_page.dart | 493 ++++++++++++------ .../exchange/widgets/exchange_card.dart | 141 ++++- lib/src/widgets/address_text_field.dart | 14 +- lib/src/widgets/base_text_form_field.dart | 9 + lib/src/widgets/top_panel.dart | 26 +- 5 files changed, 484 insertions(+), 199 deletions(-) diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 7993247c1..92e4bbd7b 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,4 +1,6 @@ import 'dart:ui'; +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; @@ -19,16 +21,17 @@ import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/widgets/top_panel.dart'; class ExchangePage extends BasePage { @override String get title => S.current.exchange; @override - bool get isModalBackButton => true; + Color get backgroundColor => PaletteDark.walletCardSubAddressField; - final Image arrowBottomPurple = - Image.asset('assets/images/arrow_bottom_purple_icon.png', height: 8); + final Image arrowBottom = + Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6); @override Widget middle(BuildContext context) { @@ -38,31 +41,34 @@ class ExchangePage extends BasePage { onPressed: () => _presentProviderPicker(context), highlightColor: Colors.transparent, splashColor: Colors.transparent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(S.of(context).exchange, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w400, - color: Theme.of(context).primaryTextTheme.title.color)), - SizedBox(width: 5), - arrowBottomPurple - ]), - Observer( - builder: (_) => Text('${exchangeStore.provider.title}', + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text(S.of(context).exchange, style: TextStyle( - fontSize: 10.0, + fontSize: 16.0, fontWeight: FontWeight.w400, - color: - Theme.of(context).primaryTextTheme.headline.color))) + color: Colors.white)), + Observer( + builder: (_) => Text('${exchangeStore.provider.title}', + style: TextStyle( + fontSize: 10.0, + fontWeight: FontWeight.w400, + color:PaletteDark.walletCardText))) + ], + ), + SizedBox(width: 5), + Padding( + padding: EdgeInsets.only(top: 8), + child: arrowBottom, + ) ], - ), + ) ); } @@ -72,14 +78,16 @@ class ExchangePage extends BasePage { return ButtonTheme( minWidth: double.minPositive, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, child: FlatButton( padding: EdgeInsets.all(0), child: Text( S.of(context).clear, style: TextStyle( - color: Theme.of(context).accentTextTheme.caption.color, + color: PaletteDark.walletCardText, fontWeight: FontWeight.w500, - fontSize: 16), + fontSize: 14), ), onPressed: () => exchangeStore.reset()), ); @@ -117,10 +125,12 @@ class ExchangeFormState extends State { final Image arrowBottomPurple = Image.asset( 'assets/images/arrow_bottom_purple_icon.png', + color: Colors.white, height: 8, ); final Image arrowBottomCakeGreen = Image.asset( 'assets/images/arrow_bottom_cake_green.png', + color: Colors.white, height: 8, ); @@ -141,156 +151,309 @@ class ExchangeFormState extends State { WidgetsBinding.instance.addPostFrameCallback( (_) => _setReactions(context, exchangeStore, walletStore)); - return Form( - key: _formKey, - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(left: 20, right: 20), - content: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, + return Container( + color: PaletteDark.historyPanel, + child: Form( + key: _formKey, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: Column( children: [ + TopPanel( + color: PaletteDark.menuList, + edgeInsets: EdgeInsets.only(bottom: 24), + widget: Column( + children: [ + TopPanel( + color: PaletteDark.walletCardSubAddressField, + widget: Observer( + builder: (_) => ExchangeCard( + key: depositKey, + title: S.of(context).you_will_send, + initialCurrency: exchangeStore.depositCurrency, + initialWalletName: depositWalletName, + initialAddress: + exchangeStore.depositCurrency == walletStore.type + ? walletStore.address + : null, + initialIsAmountEditable: true, + initialIsAddressEditable: true, + isAmountEstimated: false, + currencies: CryptoCurrency.all, + onCurrencySelected: (currency) => + exchangeStore.changeDepositCurrency(currency: currency), + imageArrow: arrowBottomPurple, + currencyButtonColor: PaletteDark.walletCardSubAddressField, + currencyValueValidator: (value) { + exchangeStore.validateCryptoCurrency(value); + return exchangeStore.errorMessage; + }, + addressTextFieldValidator: (value) { + exchangeStore.validateAddress(value, + cryptoCurrency: exchangeStore.depositCurrency); + return exchangeStore.errorMessage; + }, + ), + ) + ), + Padding( + padding: EdgeInsets.only(top: 32, left: 24, right: 24), + child: Observer( + builder: (_) => ExchangeCard( + key: receiveKey, + title: S.of(context).you_will_get, + initialCurrency: exchangeStore.receiveCurrency, + initialWalletName: receiveWalletName, + initialAddress: + exchangeStore.receiveCurrency == walletStore.type + ? walletStore.address + : null, + initialIsAmountEditable: false, + initialIsAddressEditable: true, + isAmountEstimated: true, + currencies: CryptoCurrency.all, + onCurrencySelected: (currency) => exchangeStore + .changeReceiveCurrency(currency: currency), + imageArrow: arrowBottomCakeGreen, + currencyButtonColor: PaletteDark.menuList, + currencyValueValidator: (value) { + exchangeStore.validateCryptoCurrency(value); + return exchangeStore.errorMessage; + }, + addressTextFieldValidator: (value) { + exchangeStore.validateAddress(value, + cryptoCurrency: exchangeStore.receiveCurrency); + return exchangeStore.errorMessage; + }, + )), + ) + ], + ) + ), Padding( - padding: EdgeInsets.only(top: 10, bottom: 20), - child: Text( - S.of(context).you_will_send, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - height: 1.1, - color: Theme.of(context).primaryTextTheme.title.color), + padding: EdgeInsets.only( + top: 32, + left: 24, + bottom: 24 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + S.of(context).send_templates, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: PaletteDark.walletCardText + ), + ) + ], ), ), - ExchangeCard( - key: depositKey, - initialCurrency: exchangeStore.depositCurrency, - initialWalletName: depositWalletName, - initialAddress: - exchangeStore.depositCurrency == walletStore.type - ? walletStore.address - : null, - initialIsAmountEditable: true, - initialIsAddressEditable: true, - isAmountEstimated: false, - currencies: CryptoCurrency.all, - onCurrencySelected: (currency) => - exchangeStore.changeDepositCurrency(currency: currency), - imageArrow: arrowBottomPurple, - currencyValueValidator: (value) { - exchangeStore.validateCryptoCurrency(value); - return exchangeStore.errorMessage; - }, - addressTextFieldValidator: (value) { - exchangeStore.validateAddress(value, - cryptoCurrency: exchangeStore.depositCurrency); - return exchangeStore.errorMessage; - }, - ), - SizedBox(height: 35), - Padding( - padding: EdgeInsets.only(bottom: 20), + Container( + height: 40, + width: double.infinity, + padding: EdgeInsets.only(left: 24), + child: Observer( + builder: (_) { + + return ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: 1, + itemBuilder: (context, index) { + + if (index == 0) { + return GestureDetector( + onTap: () {}, + child: Container( + padding: EdgeInsets.only(right: 10), + child: DottedBorder( + borderType: BorderType.RRect, + dashPattern: [8, 4], + color: PaletteDark.menuList, + strokeWidth: 2, + radius: Radius.circular(20), + child: Container( + height: 40, + width: 75, + padding: EdgeInsets.only(left: 10, right: 10), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + color: Colors.transparent, + ), + child: Text( + S.of(context).send_new, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: PaletteDark.walletCardText + ), + ), + ) + ), + ), + ); + } + + index -= 1; + + return Container(); + } + ); + } + ), + ) + ], + ), + + /*SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Padding( + padding: EdgeInsets.only(top: 10, bottom: 20), child: Text( - S.of(context).you_will_get, + S.of(context).you_will_send, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, height: 1.1, - color: - Theme.of(context).primaryTextTheme.title.color), - )), - Observer( - builder: (_) => ExchangeCard( - key: receiveKey, - initialCurrency: exchangeStore.receiveCurrency, - initialWalletName: receiveWalletName, - initialAddress: - exchangeStore.receiveCurrency == walletStore.type - ? walletStore.address - : null, - initialIsAmountEditable: false, - initialIsAddressEditable: true, - isAmountEstimated: true, - currencies: CryptoCurrency.all, - onCurrencySelected: (currency) => exchangeStore - .changeReceiveCurrency(currency: currency), - imageArrow: arrowBottomCakeGreen, - currencyValueValidator: (value) { - exchangeStore.validateCryptoCurrency(value); - return exchangeStore.errorMessage; - }, - addressTextFieldValidator: (value) { - exchangeStore.validateAddress(value, - cryptoCurrency: exchangeStore.receiveCurrency); - return exchangeStore.errorMessage; - }, - )), - ], - ), - ), - bottomSectionPadding: EdgeInsets.only(top: 35, left: 20, right: 20), - bottomSection: Column(children: [ - Padding( - padding: EdgeInsets.only(bottom: 15), - child: Observer(builder: (_) { - final description = - exchangeStore.provider is XMRTOExchangeProvider - ? S.of(context).amount_is_guaranteed - : S.of(context).amount_is_estimate; - return Center( - child: Text( - description, - style: TextStyle(color: Palette.blueGrey, fontSize: 12), + color: Theme.of(context).primaryTextTheme.title.color), + ), + ), + ExchangeCard( + key: depositKey, + initialCurrency: exchangeStore.depositCurrency, + initialWalletName: depositWalletName, + initialAddress: + exchangeStore.depositCurrency == walletStore.type + ? walletStore.address + : null, + initialIsAmountEditable: true, + initialIsAddressEditable: true, + isAmountEstimated: false, + currencies: CryptoCurrency.all, + /*onCurrencySelected: (currency) => + exchangeStore.changeDepositCurrency(currency: currency),*/ + imageArrow: arrowBottomPurple, + currencyValueValidator: (value) { + exchangeStore.validateCryptoCurrency(value); + return exchangeStore.errorMessage; + }, + addressTextFieldValidator: (value) { + exchangeStore.validateAddress(value, + cryptoCurrency: exchangeStore.depositCurrency); + return exchangeStore.errorMessage; + }, + ), + SizedBox(height: 35), + Padding( + padding: EdgeInsets.only(bottom: 20), + child: Text( + S.of(context).you_will_get, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + height: 1.1, + color: + Theme.of(context).primaryTextTheme.title.color), + )), + Observer( + builder: (_) => ExchangeCard( + key: receiveKey, + initialCurrency: exchangeStore.receiveCurrency, + initialWalletName: receiveWalletName, + initialAddress: + exchangeStore.receiveCurrency == walletStore.type + ? walletStore.address + : null, + initialIsAmountEditable: false, + initialIsAddressEditable: true, + isAmountEstimated: true, + currencies: CryptoCurrency.all, + /*onCurrencySelected: (currency) => exchangeStore + .changeReceiveCurrency(currency: currency),*/ + imageArrow: arrowBottomCakeGreen, + currencyValueValidator: (value) { + exchangeStore.validateCryptoCurrency(value); + return exchangeStore.errorMessage; + }, + addressTextFieldValidator: (value) { + exchangeStore.validateAddress(value, + cryptoCurrency: exchangeStore.receiveCurrency); + return exchangeStore.errorMessage; + }, + )), + ], + ), + ),*/ + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Column(children: [ + Padding( + padding: EdgeInsets.only(bottom: 15), + child: Observer(builder: (_) { + final description = + exchangeStore.provider is XMRTOExchangeProvider + ? S.of(context).amount_is_guaranteed + : S.of(context).amount_is_estimate; + return Center( + child: Text( + description, + style: TextStyle( + color: PaletteDark.walletCardText, + fontSize: 12 + ), + ), + ); + }), + ), + Observer( + builder: (_) => LoadingPrimaryButton( + text: S.of(context).exchange, + onPressed: () { + if (_formKey.currentState.validate()) { + exchangeStore.createTrade(); + } + }, + color: Colors.blue, + textColor: Colors.white, + isLoading: exchangeStore.tradeState is TradeIsCreating, + )), + /*Observer(builder: (_) { + final title = exchangeStore.provider.description.title; + var imageSrc = ''; + + switch (exchangeStore.provider.description) { + case ExchangeProviderDescription.xmrto: + imageSrc = 'assets/images/xmr_btc.png'; + break; + case ExchangeProviderDescription.changeNow: + imageSrc = 'assets/images/change_now.png'; + break; + case ExchangeProviderDescription.morphToken: + imageSrc = 'assets/images/morph_icon.png'; + break; + } + + return Padding( + padding: EdgeInsets.only(top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset(imageSrc), + SizedBox(width: 10), + Text( + S.of(context).powered_by(title), + style: TextStyle(fontSize: 14, color: Palette.powered), + ) + ], ), ); - }), - ), - Observer( - builder: (_) => LoadingPrimaryButton( - text: S.of(context).exchange, - onPressed: () { - if (_formKey.currentState.validate()) { - exchangeStore.createTrade(); - } - }, - color: Theme.of(context) - .primaryTextTheme - .button - .backgroundColor, - textColor: Theme.of(context).primaryTextTheme.button.color, - isLoading: exchangeStore.tradeState is TradeIsCreating, - )), - Observer(builder: (_) { - final title = exchangeStore.provider.description.title; - var imageSrc = ''; - - switch (exchangeStore.provider.description) { - case ExchangeProviderDescription.xmrto: - imageSrc = 'assets/images/xmr_btc.png'; - break; - case ExchangeProviderDescription.changeNow: - imageSrc = 'assets/images/change_now.png'; - break; - case ExchangeProviderDescription.morphToken: - imageSrc = 'assets/images/morph_icon.png'; - break; - } - - return Padding( - padding: EdgeInsets.only(top: 20, bottom: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset(imageSrc), - SizedBox(width: 10), - Text( - S.of(context).powered_by(title), - style: TextStyle(fontSize: 14, color: Palette.powered), - ) - ], - ), - ); - }) - ]), - )); + })*/ + ]), + )), + ); } void _setReactions( diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 833b18895..06430f074 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -5,10 +5,12 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/address_text_field.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; class ExchangeCard extends StatefulWidget { ExchangeCard( {Key key, + this.title = '', this.initialCurrency, this.initialAddress, this.initialWalletName, @@ -18,12 +20,14 @@ class ExchangeCard extends StatefulWidget { this.currencies, this.onCurrencySelected, this.imageArrow, + this.currencyButtonColor = Colors.transparent, this.currencyValueValidator, this.addressTextFieldValidator}) : super(key: key); final List currencies; final Function(CryptoCurrency) onCurrencySelected; + final String title; final CryptoCurrency initialCurrency; final String initialWalletName; final String initialAddress; @@ -31,6 +35,7 @@ class ExchangeCard extends StatefulWidget { final bool initialIsAddressEditable; final bool isAmountEstimated; final Image imageArrow; + final Color currencyButtonColor; final FormFieldValidator currencyValueValidator; final FormFieldValidator addressTextFieldValidator; @@ -42,6 +47,7 @@ class ExchangeCardState extends State { final addressController = TextEditingController(); final amountController = TextEditingController(); + String _title; String _min; String _max; CryptoCurrency _selectedCurrency; @@ -52,6 +58,7 @@ class ExchangeCardState extends State { @override void initState() { + _title = widget.title; _isAmountEditable = widget.initialIsAmountEditable; _isAddressEditable = widget.initialIsAddressEditable; _walletName = widget.initialWalletName; @@ -103,33 +110,117 @@ class ExchangeCardState extends State { @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.fromLTRB(22, 15, 22, 30), width: double.infinity, - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.all(Radius.circular(12))), - child: Column(children: [ - _isAmountEstimated != null && _isAmountEstimated - ? Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - SizedBox( - height: 30, - child: Container( - padding: EdgeInsets.fromLTRB(10, 5, 10, 5), - decoration: BoxDecoration( - color: Palette.lightGrey, - borderRadius: BorderRadius.circular(10.0)), - child: Text( - S.of(context).estimated, - style: TextStyle( - fontSize: 14, - color: Palette.wildDarkBlue, - fontWeight: FontWeight.bold), - ), + color: Colors.transparent, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + _title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: PaletteDark.walletCardText + ), + ) + ], + ), + Padding( + padding: EdgeInsets.only(top: 10), + child: Stack( + children: [ + BaseTextFormField( + controller: amountController, + enabled: _isAmountEditable, + textAlign: TextAlign.left, + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: true), + inputFormatters: [ + BlacklistingTextInputFormatter( + RegExp('[\\-|\\ |\\,]')) + ], + hintText: '0.0000', + borderColor: PaletteDark.borderCardColor, + validator: widget.currencyValueValidator + ), + Positioned( + bottom: 8, + right: 0, + child: Container( + height: 32, + padding: EdgeInsets.only(left: 10), + color: widget.currencyButtonColor, + child: InkWell( + onTap: () => _presentPicker(context), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _selectedCurrency.toString(), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white)), + Padding( + padding: EdgeInsets.only(left: 5), + child: widget.imageArrow, + ) + ]), ), ), - ]) - : Container(), - Container( + ) + ], + ) + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _min != null + ? Text( + S.of(context).min_value( + _min, _selectedCurrency.toString()), + style: TextStyle( + fontSize: 10, + height: 1.2, + color: PaletteDark.walletCardText), + ) + : Offstage(), + _min != null ? SizedBox(width: 10) : Offstage(), + _max != null + ? Text( + S.of(context).max_value( + _max, _selectedCurrency.toString()), + style: TextStyle( + fontSize: 10, + height: 1.2, + color: PaletteDark.walletCardText)) + : Offstage(), + ]), + ), + Padding( + padding: EdgeInsets.only(top: 10), + child: AddressTextField( + controller: addressController, + isActive: _isAddressEditable, + options: _isAddressEditable + ? _walletName != null + ? [] + : [ + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook, + ] + : [], + isBorderExist: false, + validator: widget.addressTextFieldValidator, + ), + ) + + /*Container( child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -254,7 +345,7 @@ class ExchangeCardState extends State { ] : [], validator: widget.addressTextFieldValidator, - ) + )*/ ]), ); } diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index d8525a4e9..6051cb826 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -19,6 +19,7 @@ class AddressTextField extends StatelessWidget { ], this.onURIScanned, this.focusNode, + this.isBorderExist = true, this.validator}); static const prefixIconWidth = 34.0; @@ -31,6 +32,7 @@ class AddressTextField extends StatelessWidget { final Function(Uri) onURIScanned; final List options; final FormFieldValidator validator; + final bool isBorderExist; FocusNode focusNode; @override @@ -114,13 +116,17 @@ class AddressTextField extends StatelessWidget { color: PaletteDark.walletCardText ), hintText: placeholder ?? S.current.widgets_address, - focusedBorder: UnderlineInputBorder( + focusedBorder: isBorderExist + ? UnderlineInputBorder( borderSide: BorderSide( color: PaletteDark.walletCardSubAddressField, - width: 1.0)), - enabledBorder: UnderlineInputBorder( + width: 1.0)) + : InputBorder.none, + enabledBorder: isBorderExist + ? UnderlineInputBorder( borderSide: - BorderSide(color: PaletteDark.walletCardSubAddressField, width: 1.0)), + BorderSide(color: PaletteDark.walletCardSubAddressField, width: 1.0)) + : InputBorder.none, ), validator: validator, ); diff --git a/lib/src/widgets/base_text_form_field.dart b/lib/src/widgets/base_text_form_field.dart index 5b76475b6..e05c3a277 100644 --- a/lib/src/widgets/base_text_form_field.dart +++ b/lib/src/widgets/base_text_form_field.dart @@ -15,6 +15,9 @@ class BaseTextFormField extends StatelessWidget { this.textColor = Colors.white, this.hintColor = PaletteDark.walletCardText, this.borderColor = PaletteDark.menuList, + this.prefix, + this.suffix, + this.enabled = true, this.validator }); @@ -29,6 +32,9 @@ class BaseTextFormField extends StatelessWidget { final Color textColor; final Color hintColor; final Color borderColor; + final Widget prefix; + final Widget suffix; + final bool enabled; final FormFieldValidator validator; @override @@ -41,11 +47,14 @@ class BaseTextFormField extends StatelessWidget { autovalidate: autovalidate, maxLines: maxLines, inputFormatters: inputFormatters, + enabled: enabled, style: TextStyle( fontSize: 16.0, color: textColor ), decoration: InputDecoration( + prefix: prefix, + suffix: suffix, hintStyle: TextStyle( color: hintColor, fontSize: 16 diff --git a/lib/src/widgets/top_panel.dart b/lib/src/widgets/top_panel.dart index dc8b5c1d5..5e2cc2c91 100644 --- a/lib/src/widgets/top_panel.dart +++ b/lib/src/widgets/top_panel.dart @@ -1,24 +1,40 @@ import 'package:flutter/material.dart'; -class TopPanel extends StatelessWidget { - TopPanel({@required this.color, @required this.widget}); +class TopPanel extends StatefulWidget { + TopPanel({ + @required this.color, + @required this.widget, + this.edgeInsets = const EdgeInsets.all(24) + }); final Color color; final Widget widget; + final EdgeInsets edgeInsets; + + @override + TopPanelState createState() => TopPanelState(color, widget, edgeInsets); +} + +class TopPanelState extends State { + TopPanelState(this._color, this._widget, this._edgeInsets); + + final Color _color; + final Widget _widget; + final EdgeInsets _edgeInsets; @override Widget build(BuildContext context) { return Container( width: double.infinity, - padding: EdgeInsets.all(24), + padding: _edgeInsets, decoration: BoxDecoration( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24) ), - color: color + color: _color ), - child: widget, + child: _widget, ); } } \ No newline at end of file From 714661e91b545cc14c5a97f30565746add196751 Mon Sep 17 00:00:00 2001 From: Oleksandr Sobol Date: Wed, 13 May 2020 21:26:15 +0300 Subject: [PATCH 2/4] CWA-212 | created currency picker and provider picker; moved template tile to widgets directory; created exchange template page --- lib/src/screens/exchange/exchange_page.dart | 5 +- .../exchange/exchange_template_page.dart | 104 +++++++++++++++ .../exchange/widgets/currency_picker.dart | 119 ++++++++++++++++++ .../exchange/widgets/exchange_card.dart | 15 ++- .../exchange/widgets/provider_picker.dart | 111 ++++++++++++++++ lib/src/screens/send/send_page.dart | 6 +- lib/src/widgets/address_text_field.dart | 8 +- .../send => }/widgets/template_tile.dart | 12 +- 8 files changed, 360 insertions(+), 20 deletions(-) create mode 100644 lib/src/screens/exchange/exchange_template_page.dart create mode 100644 lib/src/screens/exchange/widgets/currency_picker.dart create mode 100644 lib/src/screens/exchange/widgets/provider_picker.dart rename lib/src/{screens/send => }/widgets/template_tile.dart (92%) diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 92e4bbd7b..54069c4f8 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -19,9 +19,9 @@ import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/top_panel.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/provider_picker.dart'; class ExchangePage extends BasePage { @override @@ -102,7 +102,7 @@ class ExchangePage extends BasePage { final selectedItem = items.indexOf(exchangeStore.provider); showDialog( - builder: (_) => Picker( + builder: (_) => ProviderPicker( items: items, selectedAtIndex: selectedItem, title: S.of(context).change_exchange_provider, @@ -184,6 +184,7 @@ class ExchangeFormState extends State { exchangeStore.changeDepositCurrency(currency: currency), imageArrow: arrowBottomPurple, currencyButtonColor: PaletteDark.walletCardSubAddressField, + addressButtonsColor: PaletteDark.menuList, currencyValueValidator: (value) { exchangeStore.validateCryptoCurrency(value); return exchangeStore.errorMessage; diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart new file mode 100644 index 000000000..e8d8dd613 --- /dev/null +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -0,0 +1,104 @@ +import 'dart:ui'; +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; +import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart'; +import 'package:cake_wallet/src/stores/exchange/limits_state.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/widgets/top_panel.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/provider_picker.dart'; + +class ExchangeTemplatePage extends BasePage { + @override + String get title => 'New template'; + + @override + Color get backgroundColor => PaletteDark.walletCardSubAddressField; + + final Image arrowBottom = + Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 8); + + @override + Widget trailing(BuildContext context) { + final exchangeStore = Provider.of(context); + + return FlatButton( + onPressed: () => _presentProviderPicker(context), + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text(S.of(context).exchange, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w400, + color: Colors.white)), + Observer( + builder: (_) => Text('${exchangeStore.provider.title}', + style: TextStyle( + fontSize: 10.0, + fontWeight: FontWeight.w400, + color:PaletteDark.walletCardText))) + ], + ), + SizedBox(width: 5), + Padding( + padding: EdgeInsets.only(top: 8), + child: arrowBottom, + ) + ], + ) + ); + } + + void _presentProviderPicker(BuildContext context) { + final exchangeStore = Provider.of(context); + final items = exchangeStore.providersForCurrentPair(); + final selectedItem = items.indexOf(exchangeStore.provider); + + showDialog( + builder: (_) => ProviderPicker( + items: items, + selectedAtIndex: selectedItem, + title: S.of(context).change_exchange_provider, + onItemSelected: (ExchangeProvider provider) => + exchangeStore.changeProvider(provider: provider)), + context: context); + } + + @override + Widget body(BuildContext context) => ExchangeTemplateForm(); +} + +class ExchangeTemplateForm extends StatefulWidget{ + @override + ExchangeTemplateFormState createState() => ExchangeTemplateFormState(); +} + +class ExchangeTemplateFormState extends State { + @override + Widget build(BuildContext context) { + return Container(); + } +} \ No newline at end of file diff --git a/lib/src/screens/exchange/widgets/currency_picker.dart b/lib/src/screens/exchange/widgets/currency_picker.dart new file mode 100644 index 000000000..f245c145e --- /dev/null +++ b/lib/src/screens/exchange/widgets/currency_picker.dart @@ -0,0 +1,119 @@ +import 'dart:ui'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; + +class CurrencyPicker extends StatelessWidget { + CurrencyPicker({ + @required this.selectedAtIndex, + @required this.items, + @required this.title, + @required this.onItemSelected, + }); + + final int selectedAtIndex; + final List items; + final String title; + final Function(CryptoCurrency) onItemSelected; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + color: Colors.transparent, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), + child: Container( + decoration: BoxDecoration(color: PaletteDark.historyPanel.withOpacity(0.75)), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.only(left: 24, right: 24), + child: Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 24), + child: GestureDetector( + onTap: () => null, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(14)), + child: Container( + height: 400, + width: 300, + color: PaletteDark.walletCardSubAddressField, + child: GridView.count( + shrinkWrap: true, + crossAxisCount: 3, + childAspectRatio: 1.25, + physics: const NeverScrollableScrollPhysics(), + crossAxisSpacing: 1, + mainAxisSpacing: 1, + children: List.generate(15, (index) { + + if (index == 14) { + return Container( + color: PaletteDark.menuList, + ); + } + + final item = items[index]; + final isItemSelected = index == selectedAtIndex; + + final color = isItemSelected + ? PaletteDark.historyPanel + : PaletteDark.menuList; + final textColor = isItemSelected + ? Colors.blue + : Colors.white; + + return GestureDetector( + onTap: () { + if (onItemSelected == null) { + return; + } + Navigator.of(context).pop(); + onItemSelected(item); + }, + child: Container( + color: color, + child: Center( + child: Text( + item.toString(), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: textColor + ), + ), + ), + ), + ); + }) + ), + ), + ), + ), + ) + ], + ), + ) + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 06430f074..15abbf4e9 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; -import 'package:cake_wallet/src/widgets/picker.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/screens/exchange/widgets/currency_picker.dart'; class ExchangeCard extends StatefulWidget { ExchangeCard( @@ -21,6 +21,7 @@ class ExchangeCard extends StatefulWidget { this.onCurrencySelected, this.imageArrow, this.currencyButtonColor = Colors.transparent, + this.addressButtonsColor = PaletteDark.walletCardSubAddressField, this.currencyValueValidator, this.addressTextFieldValidator}) : super(key: key); @@ -36,6 +37,7 @@ class ExchangeCard extends StatefulWidget { final bool isAmountEstimated; final Image imageArrow; final Color currencyButtonColor; + final Color addressButtonsColor; final FormFieldValidator currencyValueValidator; final FormFieldValidator addressTextFieldValidator; @@ -216,6 +218,7 @@ class ExchangeCardState extends State { ] : [], isBorderExist: false, + buttonColor: widget.addressButtonsColor, validator: widget.addressTextFieldValidator, ), ) @@ -352,14 +355,14 @@ class ExchangeCardState extends State { void _presentPicker(BuildContext context) { showDialog( - builder: (_) => Picker( - items: widget.currencies, + builder: (_) => CurrencyPicker( selectedAtIndex: widget.currencies.indexOf(_selectedCurrency), + items: widget.currencies, title: S.of(context).change_currency, onItemSelected: (CryptoCurrency item) => - widget.onCurrencySelected != null - ? widget.onCurrencySelected(item) - : null), + widget.onCurrencySelected != null + ? widget.onCurrencySelected(item) + : null), context: context); } } diff --git a/lib/src/screens/exchange/widgets/provider_picker.dart b/lib/src/screens/exchange/widgets/provider_picker.dart new file mode 100644 index 000000000..637312076 --- /dev/null +++ b/lib/src/screens/exchange/widgets/provider_picker.dart @@ -0,0 +1,111 @@ +import 'dart:ui'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; + +class ProviderPicker extends StatelessWidget { + ProviderPicker({ + @required this.selectedAtIndex, + @required this.items, + @required this.title, + @required this.onItemSelected, + }); + + final int selectedAtIndex; + final List items; + final String title; + final Function(ExchangeProvider) onItemSelected; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + color: Colors.transparent, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), + child: Container( + decoration: BoxDecoration(color: PaletteDark.historyPanel.withOpacity(0.75)), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.only(left: 24, right: 24), + child: Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: GestureDetector( + onTap: () => null, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(14)), + child: Container( + height: 233, + color: PaletteDark.menuList, + child: ListView.separated( + separatorBuilder: (context, index) => Divider( + color: PaletteDark.mainBackgroundColor, + height: 1, + ), + itemCount: items == null ? 0 : items.length, + itemBuilder: (context, index) { + final item = items[index]; + final isItemSelected = index == selectedAtIndex; + + final color = isItemSelected + ? PaletteDark.menuHeader + : Colors.transparent; + final textColor = isItemSelected + ? Colors.blue + : Colors.white; + + return GestureDetector( + onTap: () { + if (onItemSelected == null) { + return; + } + Navigator.of(context).pop(); + onItemSelected(item); + }, + child: Container( + height: 77, + padding: EdgeInsets.only(left: 24, right: 24), + alignment: Alignment.center, + color: color, + child: Text( + item.toString(), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: textColor, + decoration: TextDecoration.none, + ), + ), + ), + ); + }, + ) + ), + ), + ), + ) + ], + ), + ) + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 1881e891c..23f32c5a5 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -27,7 +27,7 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart'; import 'package:cake_wallet/src/screens/send/widgets/sending_alert.dart'; -import 'package:cake_wallet/src/screens/send/widgets/template_tile.dart'; +import 'package:cake_wallet/src/widgets/template_tile.dart'; import 'package:cake_wallet/src/stores/send_template/send_template_store.dart'; class SendPage extends BasePage { @@ -394,9 +394,9 @@ class SendFormState extends State { final template = sendTemplateStore.templates[index]; return TemplateTile( - name: template.name, + to: template.name, amount: template.amount, - cryptoCurrency: template.cryptoCurrency, + from: template.cryptoCurrency, onTap: () { _addressController.text = template.address; _cryptoAmountController.text = template.amount; diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 6051cb826..647dd80ba 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -20,6 +20,7 @@ class AddressTextField extends StatelessWidget { this.onURIScanned, this.focusNode, this.isBorderExist = true, + this.buttonColor = PaletteDark.walletCardSubAddressField, this.validator}); static const prefixIconWidth = 34.0; @@ -33,6 +34,7 @@ class AddressTextField extends StatelessWidget { final List options; final FormFieldValidator validator; final bool isBorderExist; + final Color buttonColor; FocusNode focusNode; @override @@ -64,7 +66,7 @@ class AddressTextField extends StatelessWidget { child: Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( - color: PaletteDark.walletCardSubAddressField, + color: buttonColor, borderRadius: BorderRadius.all(Radius.circular(6))), child: Image.asset('assets/images/qr_code_icon.png')), @@ -82,7 +84,7 @@ class AddressTextField extends StatelessWidget { child: Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( - color: PaletteDark.walletCardSubAddressField, + color: buttonColor, borderRadius: BorderRadius.all(Radius.circular(6))), child: Image.asset( @@ -101,7 +103,7 @@ class AddressTextField extends StatelessWidget { child: Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( - color: PaletteDark.walletCardSubAddressField, + color: buttonColor, borderRadius: BorderRadius.all(Radius.circular(6))), child: Image.asset( diff --git a/lib/src/screens/send/widgets/template_tile.dart b/lib/src/widgets/template_tile.dart similarity index 92% rename from lib/src/screens/send/widgets/template_tile.dart rename to lib/src/widgets/template_tile.dart index ff3bac513..63cf9b3b0 100644 --- a/lib/src/screens/send/widgets/template_tile.dart +++ b/lib/src/widgets/template_tile.dart @@ -3,15 +3,15 @@ import 'package:cake_wallet/palette.dart'; class TemplateTile extends StatelessWidget { TemplateTile({ - @required this.name, + @required this.to, @required this.amount, - @required this.cryptoCurrency, + @required this.from, @required this.onTap }); - final String name; + final String to; final String amount; - final String cryptoCurrency; + final String from; final VoidCallback onTap; final toIcon = Image.asset('assets/images/to_icon.png'); @@ -44,7 +44,7 @@ class TemplateTile extends StatelessWidget { Padding( padding: EdgeInsets.only(left: 5), child: Text( - cryptoCurrency, + from, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -59,7 +59,7 @@ class TemplateTile extends StatelessWidget { Padding( padding: EdgeInsets.only(left: 5), child: Text( - name, + to, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, From b1f8658eec857d0d4d2e3ffbaf4c85d67c86567b Mon Sep 17 00:00:00 2001 From: Oleksandr Sobol Date: Thu, 14 May 2020 20:29:10 +0300 Subject: [PATCH 3/4] CWA-212 | created exchange template and exchange template store; added exchange cards to exchange template page; added exchange templates to exchange page; added provider icons to provider picker --- assets/images/2.0x/morph_icon.png | Bin 1741 -> 1552 bytes assets/images/3.0x/morph_icon.png | Bin 2685 -> 2358 bytes assets/images/morph_icon.png | Bin 895 -> 752 bytes lib/main.dart | 6 + lib/router.dart | 20 ++ lib/routes.dart | 1 + .../domain/exchange/exchange_template.dart | 35 ++ lib/src/screens/exchange/exchange_page.dart | 189 ++++------ .../exchange/exchange_template_page.dart | 335 +++++++++++++++++- .../exchange/widgets/exchange_card.dart | 129 +------ .../exchange/widgets/provider_picker.dart | 31 +- lib/src/screens/send/send_page.dart | 2 +- lib/src/stores/exchange/exchange_store.dart | 14 +- .../exchange_template_store.dart | 40 +++ 14 files changed, 537 insertions(+), 265 deletions(-) create mode 100644 lib/src/domain/exchange/exchange_template.dart create mode 100644 lib/src/stores/exchange_template/exchange_template_store.dart diff --git a/assets/images/2.0x/morph_icon.png b/assets/images/2.0x/morph_icon.png index a897a73b13895ce7d93f1ddedd502e8a27d1886e..0cc2c3bac7a7a019232a14021e6984b00df28cff 100644 GIT binary patch delta 1485 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 Wn3>rPb&LQ2002ovP6b4+LSTZLo-8N; diff --git a/assets/images/3.0x/morph_icon.png b/assets/images/3.0x/morph_icon.png index db98d49da84ddf84192f2d49f622b766254a126c..bdf991cf2b05ca8b2e4dadefbf69e20d5ad8fdbb 100644 GIT binary patch delta 2297 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 TIFzl(00000NkvXXu0mjf|3+}6 delta 2627 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 N002ovPDHLkV1kL$NSpuw delta 823 zcmV-71IYaF1^)(+Reu8%Nkl&B&h$u$HA0?>iI3gqs5rLWX6vr8znR~l$#(B;- zZ)67jHt*hZ?)|=V?>X;W!X9X_HP}>^-3p?MB!pc6_K1fx2!B3Jq&NaRIOxz`dXjOF znX$>%WDhDz4}_>f3XsCRAsJ7TzLTSA5LL;OtVT}~&O!LjnKC7>#fF1{7UaA_ z&k56KA;#_=Ab+_In@ZH94=55n`w{h=Js-r@rh>&eN{jL6F@~a3Dn^F43|o+IF!+4$ zg1TIq$;X?!-{GF+N@5N89~XaMP%x>iNO zk@yO8&3_6cfyU%LdCWYcpRBTB?hyGl7@l?=#;X@w_`AGgCqZyJxjDTM(|VGfX1y_%GAnk|?f%fQkv3kIH96MuZK`$Je<4B_)fb8`KS(0fr#vtzQ^ zA+3HygxG&H7DlrR|4}Z#ODndPZpt0CH~rQJ&=#YcyU&lKCHriz(Trade.boxName, encryptionKey: tradesBoxKey); final walletInfoSource = await Hive.openBox(WalletInfo.boxName); final templates = await Hive.openBox