mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-22 10:45:08 +00:00
litecoin fixes, sign form fixes, use new walletAddressPicker
This commit is contained in:
parent
b08bac577d
commit
dd486a29e7
11 changed files with 354 additions and 213 deletions
|
@ -9,6 +9,7 @@ import 'package:blockchain_utils/exception/exception.dart';
|
||||||
import 'package:blockchain_utils/hex/hex.dart';
|
import 'package:blockchain_utils/hex/hex.dart';
|
||||||
import 'package:blockchain_utils/numbers/bigint_utils.dart';
|
import 'package:blockchain_utils/numbers/bigint_utils.dart';
|
||||||
import 'package:blockchain_utils/signer/bitcoin_signer.dart';
|
import 'package:blockchain_utils/signer/bitcoin_signer.dart';
|
||||||
|
import 'package:blockchain_utils/signer/ecdsa_signing_key.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
@ -137,7 +138,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
final HD = index == null ? hd : hd.derive(index);
|
final HD = index == null ? hd : hd.derive(index);
|
||||||
final priv = ECPrivate.fromHex(HD.privKey!);
|
final priv = ECPrivate.fromHex(HD.privKey!);
|
||||||
String messagePrefix = '\x19Litecoin Signed Message:\n';
|
String messagePrefix = '\x19Litecoin Signed Message:\n';
|
||||||
return priv.signMessage(utf8.encode(message), messagePrefix: messagePrefix);
|
|
||||||
|
final privateKey = ECDSAPrivateKey.fromBytes(
|
||||||
|
priv.toBytes(),
|
||||||
|
Curves.generatorSecp256k1,
|
||||||
|
);
|
||||||
|
final signature =
|
||||||
|
signLitecoinMessage(utf8.encode(message), messagePrefix, privateKey: privateKey);
|
||||||
|
return BytesUtils.toHexString(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> _magicPrefix(List<int> message, List<int> messagePrefix) {
|
List<int> _magicPrefix(List<int> message, List<int> messagePrefix) {
|
||||||
|
@ -146,6 +154,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
return [...messagePrefix, ...encodeLength, ...message];
|
return [...messagePrefix, ...encodeLength, ...message];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<int> signLitecoinMessage(List<int> message, String messagePrefix,
|
||||||
|
{required ECDSAPrivateKey privateKey}) {
|
||||||
|
final messgaeHash = QuickCrypto.sha256Hash(magicMessage(message, messagePrefix));
|
||||||
|
final signingKey = EcdsaSigningKey(privateKey);
|
||||||
|
ECDSASignature ecdsaSign =
|
||||||
|
signingKey.signDigestDeterminstic(digest: messgaeHash, hashFunc: () => SHA256());
|
||||||
|
final n = Curves.generatorSecp256k1.order! >> 1;
|
||||||
|
BigInt newS;
|
||||||
|
if (ecdsaSign.s.compareTo(n) > 0) {
|
||||||
|
newS = Curves.generatorSecp256k1.order! - ecdsaSign.s;
|
||||||
|
} else {
|
||||||
|
newS = ecdsaSign.s;
|
||||||
|
}
|
||||||
|
final newSignature = ECDSASignature(ecdsaSign.r, newS);
|
||||||
|
return newSignature.toBytes(BitcoinSignerUtils.baselen);
|
||||||
|
}
|
||||||
|
|
||||||
List<int> magicMessage(List<int> message, String messagePrefix) {
|
List<int> magicMessage(List<int> message, String messagePrefix) {
|
||||||
final prefixBytes = StringUtils.encode(messagePrefix);
|
final prefixBytes = StringUtils.encode(messagePrefix);
|
||||||
final magic = _magicPrefix(message, prefixBytes);
|
final magic = _magicPrefix(message, prefixBytes);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import 'package:cake_wallet/entities/background_tasks.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||||
import 'package:cw_core/receive_page_option.dart';
|
import 'package:cw_core/receive_page_option.dart';
|
||||||
|
@ -757,6 +758,8 @@ Future<void> setup({
|
||||||
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
|
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
|
||||||
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
||||||
|
|
||||||
|
getIt.registerFactory(() => AddressListPage(getIt.get<WalletAddressListViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
final appStore = getIt.get<AppStore>();
|
final appStore = getIt.get<AppStore>();
|
||||||
return NodeListViewModel(_nodeSource, appStore);
|
return NodeListViewModel(_nodeSource, appStore);
|
||||||
|
|
|
@ -19,6 +19,7 @@ import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
|
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||||
|
@ -377,6 +378,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
builder: (_) => getIt.get<ContactListPage>(param1: selectedCurrency));
|
builder: (_) => getIt.get<ContactListPage>(param1: selectedCurrency));
|
||||||
|
|
||||||
|
case Routes.pickerWalletAddress:
|
||||||
|
return MaterialPageRoute<void>(builder: (_) => getIt.get<AddressListPage>());
|
||||||
|
|
||||||
case Routes.addressBookAddContact:
|
case Routes.addressBookAddContact:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
|
builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
|
||||||
|
|
|
@ -29,6 +29,7 @@ class Routes {
|
||||||
static const nanoAccountCreation = '/nano_account_new';
|
static const nanoAccountCreation = '/nano_account_new';
|
||||||
static const addressBook = '/address_book';
|
static const addressBook = '/address_book';
|
||||||
static const pickerAddressBook = '/picker_address_book';
|
static const pickerAddressBook = '/picker_address_book';
|
||||||
|
static const pickerWalletAddress = '/picker_wallet_address';
|
||||||
static const addressBookAddContact = '/address_book_add_contact';
|
static const addressBookAddContact = '/address_book_add_contact';
|
||||||
static const showKeys = '/show_keys';
|
static const showKeys = '/show_keys';
|
||||||
static const exchangeConfirm = '/exchange_confirm';
|
static const exchangeConfirm = '/exchange_confirm';
|
||||||
|
|
|
@ -51,59 +51,11 @@ class SignPage extends BasePage {
|
||||||
final List<Widget> _pages;
|
final List<Widget> _pages;
|
||||||
final GlobalKey<SignFormState> signFormKey;
|
final GlobalKey<SignFormState> signFormKey;
|
||||||
final GlobalKey<VerifyFormState> verifyFormKey;
|
final GlobalKey<VerifyFormState> verifyFormKey;
|
||||||
|
bool _isEffectsInstalled = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
reaction((_) => signViewModel.state, (ExecutionState state) {
|
_setEffects(context);
|
||||||
if (state is FailureState) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
showPopUp<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) {
|
|
||||||
return AlertWithOneAction(
|
|
||||||
alertTitle: S.current.error,
|
|
||||||
alertContent: state.error,
|
|
||||||
buttonText: S.of(context).ok,
|
|
||||||
buttonAction: () => Navigator.of(context).pop(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (state is ExecutedSuccessfullyState) {
|
|
||||||
if (signViewModel.isSigning) {
|
|
||||||
signFormKey.currentState!.signatureController.text = state.payload as String;
|
|
||||||
} else {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
showPopUp<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) {
|
|
||||||
return AlertWithOneAction(
|
|
||||||
alertTitle: S.current.successful,
|
|
||||||
alertContent: S.current.message_verified,
|
|
||||||
buttonText: S.of(context).ok,
|
|
||||||
buttonAction: () => Navigator.of(context).pop(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) {
|
|
||||||
// walletRestoreViewModel.isButtonEnabled = false;
|
|
||||||
|
|
||||||
// walletRestoreFromSeedFormKey
|
|
||||||
// .currentState!.blockchainHeightKey.currentState!.restoreHeightController.text = '';
|
|
||||||
// walletRestoreFromSeedFormKey
|
|
||||||
// .currentState!.blockchainHeightKey.currentState!.dateController.text = '';
|
|
||||||
// walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text = '';
|
|
||||||
|
|
||||||
// walletRestoreFromKeysFormKey
|
|
||||||
// .currentState!.blockchainHeightKey.currentState!.restoreHeightController.text = '';
|
|
||||||
// walletRestoreFromKeysFormKey
|
|
||||||
// .currentState!.blockchainHeightKey.currentState!.dateController.text = '';
|
|
||||||
// walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text = '';
|
|
||||||
// });
|
|
||||||
|
|
||||||
return KeyboardActions(
|
return KeyboardActions(
|
||||||
config: KeyboardActionsConfig(
|
config: KeyboardActionsConfig(
|
||||||
|
@ -173,7 +125,7 @@ class SignPage extends BasePage {
|
||||||
.extension<WalletListTheme>()!
|
.extension<WalletListTheme>()!
|
||||||
.restoreWalletButtonTextColor,
|
.restoreWalletButtonTextColor,
|
||||||
isLoading: signViewModel.state is IsExecutingState,
|
isLoading: signViewModel.state is IsExecutingState,
|
||||||
// isDisabled: !signViewModel.isButtonEnabled,
|
isDisabled: signViewModel.state is IsExecutingState,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -188,6 +140,48 @@ class SignPage extends BasePage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setEffects(BuildContext context) async {
|
||||||
|
if (_isEffectsInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isEffectsInstalled = true;
|
||||||
|
|
||||||
|
reaction((_) => signViewModel.state, (ExecutionState state) {
|
||||||
|
if (state is FailureState) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return AlertWithOneAction(
|
||||||
|
alertTitle: S.current.error,
|
||||||
|
alertContent: state.error,
|
||||||
|
buttonText: S.of(context).ok,
|
||||||
|
buttonAction: () => Navigator.of(context).pop(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (state is ExecutedSuccessfullyState) {
|
||||||
|
if (signViewModel.isSigning) {
|
||||||
|
signFormKey.currentState!.signatureController.text = state.payload as String;
|
||||||
|
} else {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return AlertWithOneAction(
|
||||||
|
alertTitle: S.current.successful,
|
||||||
|
alertContent: S.current.message_verified,
|
||||||
|
buttonText: S.of(context).ok,
|
||||||
|
buttonAction: () => Navigator.of(context).pop(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _confirmForm(BuildContext context) async {
|
Future<void> _confirmForm(BuildContext context) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ class SignFormState extends State<SignForm> {
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
AddressTextField(
|
AddressTextField(
|
||||||
controller: addressController,
|
controller: addressController,
|
||||||
options: [AddressTextFieldOption.paste, AddressTextFieldOption.addressBook],
|
options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses],
|
||||||
buttonColor: Theme.of(context).hintColor,
|
buttonColor: Theme.of(context).hintColor,
|
||||||
onSelectedContact: (contact) {
|
onSelectedContact: (contact) {
|
||||||
addressController.text = contact.address;
|
addressController.text = contact.address;
|
||||||
|
|
|
@ -70,7 +70,7 @@ class VerifyFormState extends State<VerifyForm> {
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
AddressTextField(
|
AddressTextField(
|
||||||
controller: addressController,
|
controller: addressController,
|
||||||
options: [AddressTextFieldOption.paste, AddressTextFieldOption.addressBook],
|
options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses],
|
||||||
buttonColor: Theme.of(context).hintColor,
|
buttonColor: Theme.of(context).hintColor,
|
||||||
onSelectedContact: (contact) {
|
onSelectedContact: (contact) {
|
||||||
addressController.text = contact.address;
|
addressController.text = contact.address;
|
||||||
|
|
31
lib/src/screens/receive/address_list_page.dart
Normal file
31
lib/src/screens/receive/address_list_page.dart
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
|
||||||
|
class AddressListPage extends BasePage {
|
||||||
|
AddressListPage(this.addressListViewModel);
|
||||||
|
|
||||||
|
final WalletAddressListViewModel addressListViewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => S.current.accounts_subaddresses;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
AddressList(
|
||||||
|
addressListViewModel: addressListViewModel,
|
||||||
|
onSelect: (String address) async {
|
||||||
|
Navigator.of(context).pop(address);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
|
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
|
||||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||||
|
@ -123,89 +124,10 @@ class ReceivePage extends BasePage {
|
||||||
amountController: _amountController,
|
amountController: _amountController,
|
||||||
isLight: currentTheme.type == ThemeType.light),
|
isLight: currentTheme.type == ThemeType.light),
|
||||||
),
|
),
|
||||||
Observer(
|
AddressList(
|
||||||
builder: (_) => ListView.separated(
|
addressListViewModel: addressListViewModel,
|
||||||
padding: EdgeInsets.all(0),
|
),
|
||||||
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: addressListViewModel.items.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final item = addressListViewModel.items[index];
|
|
||||||
Widget cell = Container();
|
|
||||||
|
|
||||||
if (item is WalletAccountListHeader) {
|
|
||||||
cell = HeaderTile(
|
|
||||||
showTrailingButton: true,
|
|
||||||
walletAddressListViewModel: addressListViewModel,
|
|
||||||
trailingButtonTap: () async {
|
|
||||||
if (addressListViewModel.type == WalletType.monero ||
|
|
||||||
addressListViewModel.type == WalletType.haven) {
|
|
||||||
await showPopUp<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => getIt.get<MoneroAccountListPage>());
|
|
||||||
} else {
|
|
||||||
await showPopUp<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => getIt.get<NanoAccountListPage>());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title: S.of(context).accounts,
|
|
||||||
trailingIcon: Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 14,
|
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is WalletAddressListHeader) {
|
|
||||||
cell = HeaderTile(
|
|
||||||
title: S.of(context).addresses,
|
|
||||||
walletAddressListViewModel: addressListViewModel,
|
|
||||||
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
|
|
||||||
showSearchButton: true,
|
|
||||||
trailingButtonTap: () =>
|
|
||||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
|
||||||
trailingIcon: Icon(
|
|
||||||
Icons.add,
|
|
||||||
size: 20,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<ReceivePageTheme>()!
|
|
||||||
.iconsColor,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is WalletAddressListItem) {
|
|
||||||
cell = Observer(builder: (_) {
|
|
||||||
final isCurrent =
|
|
||||||
item.address == addressListViewModel.address.address;
|
|
||||||
final backgroundColor = isCurrent
|
|
||||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
|
||||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
|
||||||
final textColor = isCurrent
|
|
||||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
|
||||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
|
||||||
|
|
||||||
return AddressCell.fromItem(item,
|
|
||||||
isCurrent: isCurrent,
|
|
||||||
hasBalance: addressListViewModel.isElectrumWallet,
|
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
textColor: textColor,
|
|
||||||
onTap: (_) => addressListViewModel.setAddress(item),
|
|
||||||
onEdit: () => Navigator.of(context)
|
|
||||||
.pushNamed(Routes.newSubaddress, arguments: item));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return index != 0
|
|
||||||
? cell
|
|
||||||
: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(30),
|
|
||||||
topRight: Radius.circular(30)),
|
|
||||||
child: cell,
|
|
||||||
);
|
|
||||||
})),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
121
lib/src/screens/receive/widgets/address_list.dart
Normal file
121
lib/src/screens/receive/widgets/address_list.dart
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import 'package:cake_wallet/di.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
|
class AddressList extends StatelessWidget {
|
||||||
|
const AddressList({
|
||||||
|
super.key,
|
||||||
|
// required this.onTapPicker,
|
||||||
|
required this.addressListViewModel,
|
||||||
|
this.onSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
final WalletAddressListViewModel addressListViewModel;
|
||||||
|
final Function(String)? onSelect;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
bool editable = onSelect == null;
|
||||||
|
return Observer(
|
||||||
|
builder: (_) => ListView.separated(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: addressListViewModel.items.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = addressListViewModel.items[index];
|
||||||
|
Widget cell = Container();
|
||||||
|
|
||||||
|
if (item is WalletAccountListHeader) {
|
||||||
|
cell = HeaderTile(
|
||||||
|
showTrailingButton: editable,
|
||||||
|
walletAddressListViewModel: addressListViewModel,
|
||||||
|
trailingButtonTap: () async {
|
||||||
|
if (addressListViewModel.type == WalletType.monero ||
|
||||||
|
addressListViewModel.type == WalletType.haven) {
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||||
|
} else {
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context, builder: (_) => getIt.get<NanoAccountListPage>());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: S.of(context).accounts,
|
||||||
|
trailingIcon: Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 14,
|
||||||
|
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is WalletAddressListHeader) {
|
||||||
|
cell = HeaderTile(
|
||||||
|
title: S.of(context).addresses,
|
||||||
|
walletAddressListViewModel: addressListViewModel,
|
||||||
|
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
|
||||||
|
showSearchButton: true,
|
||||||
|
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||||
|
trailingIcon: Icon(
|
||||||
|
Icons.add,
|
||||||
|
size: 20,
|
||||||
|
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is WalletAddressListItem) {
|
||||||
|
cell = Observer(builder: (_) {
|
||||||
|
final isCurrent = item.address == addressListViewModel.address.address && editable;
|
||||||
|
final backgroundColor = isCurrent
|
||||||
|
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
||||||
|
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
||||||
|
final textColor = isCurrent
|
||||||
|
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
||||||
|
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
||||||
|
|
||||||
|
return AddressCell.fromItem(
|
||||||
|
item,
|
||||||
|
isCurrent: isCurrent,
|
||||||
|
hasBalance: addressListViewModel.isElectrumWallet,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
textColor: textColor,
|
||||||
|
onTap: (_) {
|
||||||
|
if (onSelect != null) {
|
||||||
|
onSelect!(item.address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addressListViewModel.setAddress(item);
|
||||||
|
},
|
||||||
|
onEdit: editable
|
||||||
|
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return index != 0
|
||||||
|
? cell
|
||||||
|
: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
||||||
|
child: cell,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||||
import 'package:cake_wallet/utils/permission_handler.dart';
|
import 'package:cake_wallet/utils/permission_handler.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
enum AddressTextFieldOption { paste, qrCode, addressBook }
|
enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses }
|
||||||
|
|
||||||
class AddressTextField extends StatelessWidget {
|
class AddressTextField extends StatelessWidget {
|
||||||
AddressTextField(
|
AddressTextField(
|
||||||
|
@ -31,6 +31,7 @@ class AddressTextField extends StatelessWidget {
|
||||||
this.validator,
|
this.validator,
|
||||||
this.onPushPasteButton,
|
this.onPushPasteButton,
|
||||||
this.onPushAddressBookButton,
|
this.onPushAddressBookButton,
|
||||||
|
this.onPushAddressPickerButton,
|
||||||
this.onSelectedContact,
|
this.onSelectedContact,
|
||||||
this.selectedCurrency});
|
this.selectedCurrency});
|
||||||
|
|
||||||
|
@ -53,6 +54,7 @@ class AddressTextField extends StatelessWidget {
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
final Function(BuildContext context)? onPushPasteButton;
|
final Function(BuildContext context)? onPushPasteButton;
|
||||||
final Function(BuildContext context)? onPushAddressBookButton;
|
final Function(BuildContext context)? onPushAddressBookButton;
|
||||||
|
final Function(BuildContext context)? onPushAddressPickerButton;
|
||||||
final Function(ContactBase contact)? onSelectedContact;
|
final Function(ContactBase contact)? onSelectedContact;
|
||||||
final CryptoCurrency? selectedCurrency;
|
final CryptoCurrency? selectedCurrency;
|
||||||
|
|
||||||
|
@ -67,16 +69,13 @@ class AddressTextField extends StatelessWidget {
|
||||||
enabled: isActive,
|
enabled: isActive,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
|
|
||||||
style: textStyle ??
|
style: textStyle ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
fontSize: 16, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
fontSize: 16, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
||||||
suffixIcon: SizedBox(
|
suffixIcon: SizedBox(
|
||||||
width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
|
width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
|
||||||
),
|
),
|
||||||
|
|
||||||
hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor),
|
hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor),
|
||||||
hintText: placeholder ?? S.current.widgets_address,
|
hintText: placeholder ?? S.current.widgets_address,
|
||||||
focusedBorder: isBorderExist
|
focusedBorder: isBorderExist
|
||||||
|
@ -101,14 +100,15 @@ class AddressTextField extends StatelessWidget {
|
||||||
top: 2,
|
top: 2,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
|
width:
|
||||||
|
(prefixIconWidth * options.length) + (spaceBetweenPrefixIcons * options.length),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: responsiveLayoutUtil.shouldRenderMobileUI
|
mainAxisAlignment: responsiveLayoutUtil.shouldRenderMobileUI
|
||||||
? MainAxisAlignment.spaceBetween
|
? MainAxisAlignment.spaceBetween
|
||||||
: MainAxisAlignment.end,
|
: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 5),
|
|
||||||
if (this.options.contains(AddressTextFieldOption.paste)) ...[
|
if (this.options.contains(AddressTextFieldOption.paste)) ...[
|
||||||
|
SizedBox(width: 5),
|
||||||
Container(
|
Container(
|
||||||
width: prefixIconWidth,
|
width: prefixIconWidth,
|
||||||
height: prefixIconHeight,
|
height: prefixIconHeight,
|
||||||
|
@ -120,8 +120,8 @@ class AddressTextField extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: buttonColor ??
|
color:
|
||||||
Theme.of(context).dialogTheme.backgroundColor,
|
buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'assets/images/paste_ios.png',
|
'assets/images/paste_ios.png',
|
||||||
|
@ -131,10 +131,12 @@ class AddressTextField extends StatelessWidget {
|
||||||
.textFieldButtonIconColor,
|
.textFieldButtonIconColor,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
if (this.options.contains(AddressTextFieldOption.qrCode) &&
|
if (this.options.contains(AddressTextFieldOption.qrCode) &&
|
||||||
DeviceInfo.instance.isMobile) ...[
|
DeviceInfo.instance.isMobile) ...[
|
||||||
|
SizedBox(width: 5),
|
||||||
Container(
|
Container(
|
||||||
width: prefixIconWidth,
|
width: prefixIconWidth,
|
||||||
height: prefixIconHeight,
|
height: prefixIconHeight,
|
||||||
|
@ -146,8 +148,8 @@ class AddressTextField extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: buttonColor ??
|
color:
|
||||||
Theme.of(context).dialogTheme.backgroundColor,
|
buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'assets/images/qr_code_icon.png',
|
'assets/images/qr_code_icon.png',
|
||||||
|
@ -157,10 +159,11 @@ class AddressTextField extends StatelessWidget {
|
||||||
.textFieldButtonIconColor,
|
.textFieldButtonIconColor,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
))
|
),
|
||||||
] else
|
),
|
||||||
SizedBox(width: 5),
|
],
|
||||||
if (this.options.contains(AddressTextFieldOption.addressBook)) ...[
|
if (this.options.contains(AddressTextFieldOption.addressBook)) ...[
|
||||||
|
SizedBox(width: 5),
|
||||||
Container(
|
Container(
|
||||||
width: prefixIconWidth,
|
width: prefixIconWidth,
|
||||||
height: prefixIconHeight,
|
height: prefixIconHeight,
|
||||||
|
@ -172,8 +175,8 @@ class AddressTextField extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: buttonColor ??
|
color:
|
||||||
Theme.of(context).dialogTheme.backgroundColor,
|
buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'assets/images/open_book.png',
|
'assets/images/open_book.png',
|
||||||
|
@ -183,8 +186,36 @@ class AddressTextField extends StatelessWidget {
|
||||||
.textFieldButtonIconColor,
|
.textFieldButtonIconColor,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
))
|
),
|
||||||
]
|
),
|
||||||
|
],
|
||||||
|
if (this.options.contains(AddressTextFieldOption.walletAddresses)) ...[
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Container(
|
||||||
|
width: prefixIconWidth,
|
||||||
|
height: prefixIconHeight,
|
||||||
|
padding: EdgeInsets.only(top: 0),
|
||||||
|
child: Semantics(
|
||||||
|
label: S.of(context).address_book,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async => _presetWalletAddressPicker(context),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/open_book.png',
|
||||||
|
color: iconColor ??
|
||||||
|
Theme.of(context)
|
||||||
|
.extension<SendPageTheme>()!
|
||||||
|
.textFieldButtonIconColor,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
@ -221,6 +252,15 @@ class AddressTextField extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _presetWalletAddressPicker(BuildContext context) async {
|
||||||
|
final address = await Navigator.of(context).pushNamed(Routes.pickerWalletAddress);
|
||||||
|
|
||||||
|
if (address is String) {
|
||||||
|
controller?.text = address;
|
||||||
|
onPushAddressPickerButton?.call(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _pasteAddress(BuildContext context) async {
|
Future<void> _pasteAddress(BuildContext context) async {
|
||||||
final clipboard = await Clipboard.getData('text/plain');
|
final clipboard = await Clipboard.getData('text/plain');
|
||||||
final address = clipboard?.text ?? '';
|
final address = clipboard?.text ?? '';
|
||||||
|
|
Loading…
Reference in a new issue