Add decred (#1938)

* decred: Add decred. (#1322)

* multi: Add initial decred screens. (#1165)

Use a mock libwallet for now.

* cw_decred: add libdcrwallet dependency and link library for android, ios and macos (#1240)

* change cw_decred from package to plugin

* add libdcrwallet dependency and link library for android, ios and macos

* remove spvwallet, make some libdcrwallet fns async, light refactor

* libdcrwallet: use json payload returns

* use specific libwallet commit hash

* decred: fix Rename wallet.

---------

Co-authored-by: JoeGruff <joegruffins@gmail.com>

* decred: Add sync.

* decred: Add send transaction.

* decred: Fix fee estimation.

* decred: List transactions.

* decred: Add rescan.

* decred: Sign message.

* decred: Add new addr and addrs.

* decred: Add change wallet pass.

* decred: Add restore from seed.

* decred: Add watching only wallets.

* decred: Enable mainnet.

* decred: Allow using blank node address.

This allows a persistent peer to be unset, falling back to decred
seeders.

* decred: Rescan from wallet birthday.

* add and update macos build scripts, update build readme, gitignore macos project.pbxproj

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* multi: hide decred rescan page if it's not ready

-  move hasRescan method to WalletBase and implement for decred

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* cw_decred: fix bug where decred wallets are not loaded after app restart

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* add buy and sell for decred via onramp

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* bug-fix: account for other send outputs that are part of the same tx

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* decred: Return address with no peers.

* decred: Update pubspec.

* decred: Add verify message.

* upgrade hive_generator dep in cw_decred

* decred: Clean up code.

---------

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com>

* fix extracted addresses not used
fix conflicts with main

* remove print [skip ci]

* minor formatting

* fix initial migration version

* add build decred script to workflow

* install go before build decred
fix switch cases

* trial 2 to fix decred build

* re-install go

* revert build script change

* refactor/clean nodes functions

* Fix address book issue
Fix send ALL (to be continued with the fees point)

* Fix transactions display issues
Add missing file

* Fix unconfirmed balance not displayed
Change Wallet order
Minor cleanup

* Fix workflow

* Fix workflow

* Fix workflow

* test

* hardcode path for now

* fix + cleanup decred build script to work on mac and linux

* Update decred build script

* Run actions on pull requests, extract commit message

* run after checkout

* add safe directory

* Get commit message from base.sha instead of last commit

* base -> head

* Do not merge main branch into pr

* [skip slack] [run tests] clone by sha

* Proper name for decred library in the build script

* Throw an error when ANDROID_HOME or ANDROID_NDK_VERSION is missing

* Fix conflicts with main

* minor code enhancement

* decred: Add used address history.  (#1941)

* decred: Update pubspec.

* decred testnet

* decred: Add used address history.

* decred: Remove default node list.

* populate transaction history before sync begins

* decred: Add some awaits.

* decred: Fix send all.

* decred: Add clang export to build script.

* decred: Update logo colors.

* cleanup cw_decred.dart

* make decred wallet addresses selectable in receive page

* decred: Always set default addr when used.

* decred: Add back default node list.

* decred: Allow creating addresses manually.

---------

Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* minor fixes and cleanup

* minor fix, feel free to test now

* - Fix transaction details
- Fix Nodes
- Add processing sync status

* Add decred info card

* push missing file

* Add missing text for decred info card

* minor: change docs link [skip ci]

* decred: Update derivation info. (#2013)

* decred: Update derivation info.

* decred: Allow unsynced unused addresses.

* decred: Update dcrwallet dep to 4.3.0.

* Merge main and fix conflicts

* Merge main and fix conflicts

* decred: Fix background sync panic. (#2080)

* decred: Run libwallet in isolate. (#2077)

* decred: Fix contact save inquiry. (#2083)

Also fix tx time and the fee shown on pending transactions.

* Disable send button in view only decred wallets

* - Fix frozen coins
- Add URI support
- Fix fees in tx details
- Handle empty coins send
- Handle wallets in address book

* Merge main

* remove print [skip ci]

* Fix restore from QR

* minor improvement for QR restore

* minor fixes [skip ci]

* decred: Get slip44 addrs before sync completes. (#2092)

* - Fix loading wallet more than one time
- Fix minor UI issue

---------

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com>
Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
This commit is contained in:
Omar Hatem
2025-03-21 04:18:47 +02:00
committed by GitHub
co-authored by JoeGruffins Wisdom Arerosuoghene Philemon Ukane Czarek Nakamoto
parent 52a39e29d4
commit 0ba54fa602
175 changed files with 7145 additions and 1115 deletions
@@ -47,6 +47,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24);
final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
Image _newWalletImage(BuildContext context) => Image.asset(
@@ -181,6 +182,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return tronIcon;
case WalletType.zano:
return zanoIcon;
case WalletType.decred:
return decredIcon;
default:
return nonWalletTypeIcon;
}
@@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_row_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/info_card.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
@@ -280,81 +281,50 @@ class CryptoBalanceWidget extends StatelessWidget {
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
marginV: 0,
marginH: 0,
customBorder: 30,
child: InfoCard(
title: S.of(context).litecoin_mweb,
subTitle: S.of(context).litecoin_mweb_description,
hint: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
mode: LaunchMode.externalApplication,
),
child: Text(
S.of(context).learn_more,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
softWrap: true,
),
),
SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _dismissMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
),
child: Text(
S.of(context).litecoin_mweb_dismiss,
style: TextStyle(color: Colors.white),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: () => _enableMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
child: Text(
S.of(context).enable,
maxLines: 1,
),
),
),
],
),
],
),
icon: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
description: S.of(context).litecoin_mweb_description,
leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
rightButtonTitle: S.of(context).enable,
image: 'assets/images/mweb_logo.png',
leftButtonAction: () => _dismissMweb(context),
rightButtonAction: () => _enableMweb(context),
hintWidget: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
mode: LaunchMode.externalApplication,
),
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Color.fromARGB(255, 11, 70, 129),
size: 40,
child: Text(
S.of(context).learn_more,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
softWrap: true,
),
),
),
),
],
if (dashboardViewModel.showDecredInfoCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: InfoCard(
title: S.of(context).decred_info_title,
description: S.of(context).decred_info_card_details,
image: 'assets/images/dcr_icon.png',
leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
rightButtonTitle: S.of(context).learn_more,
leftButtonAction: () => dashboardViewModel.dismissDecredInfoCard(),
rightButtonAction: () => launchUrl(Uri.parse("https://docs.cakewallet.com/cryptos/decred/#spv-sync")),
),
),
],
],
);
}),
@@ -75,8 +75,10 @@ class NavigationDock extends StatelessWidget {
.labelTextColor,
),
title: action.name(context),
onClick: () async =>
await action.onTap(context, dashboardViewModel),
onClick: (action.isEnabled?.call(dashboardViewModel) ?? true)
? () async =>
await action.onTap(context, dashboardViewModel)
: null,
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
? null
: Theme.of(context)
@@ -0,0 +1,88 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:flutter/material.dart';
class InfoCard extends StatelessWidget {
final String leftButtonTitle;
final String rightButtonTitle;
final String title;
final String description;
final String image;
final Function() leftButtonAction;
final Function() rightButtonAction;
final Widget? hintWidget;
const InfoCard({
Key? key,
required this.title,
required this.description,
required this.leftButtonTitle,
required this.rightButtonTitle,
required this.leftButtonAction,
required this.rightButtonAction,
required this.image,
this.hintWidget,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DashBoardRoundedCardWidget(
marginH: 0,
marginV: 0,
customBorder: 30,
title: title,
subTitle: description,
hint: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (hintWidget != null) hintWidget!,
if (hintWidget != null) SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: leftButtonAction,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
),
child: Text(
leftButtonTitle,
style: TextStyle(color: Colors.white),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: rightButtonAction,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
child: Text(
rightButtonTitle,
maxLines: 1,
),
),
),
],
),
],
),
onTap: () => {},
icon: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: CakeImageWidget(
imageUrl: image,
height: 40,
width: 40,
),
),
);
}
}
@@ -38,7 +38,8 @@ class MenuWidgetState extends State<MenuWidget> {
this.solanaIcon = Image.asset('assets/images/sol_icon.png'),
this.tronIcon = Image.asset('assets/images/trx_icon.png'),
this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'),
this.zanoIcon = Image.asset('assets/images/zano_icon.png');
this.zanoIcon = Image.asset('assets/images/zano_icon.png'),
this.decredIcon = Image.asset('assets/images/decred_menu.png');
final largeScreen = 731;
@@ -64,6 +65,7 @@ class MenuWidgetState extends State<MenuWidget> {
Image tronIcon;
Image wowneroIcon;
Image zanoIcon;
Image decredIcon;
@override
void initState() {
@@ -250,6 +252,8 @@ class MenuWidgetState extends State<MenuWidget> {
return wowneroIcon;
case WalletType.zano:
return zanoIcon;
case WalletType.decred:
return decredIcon;
default:
throw Exception('No icon for ${type.toString()}');
}
@@ -22,8 +22,8 @@ class SyncIndicator extends StatelessWidget {
builder: (_) {
final syncIndicatorWidth = 237.0;
final status = dashboardViewModel.status;
final statusText = status != null ? syncStatusTitle(status) : '';
final progress = status != null ? status.progress() : 0.0;
final statusText = syncStatusTitle(status);
final progress = status.progress();
final indicatorOffset = progress * syncIndicatorWidth;
final indicatorWidth = progress < 1
? indicatorOffset > 0 ? indicatorOffset : 0.0
@@ -274,7 +274,8 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
],
);
}),
if (widget.privacySettingsViewModel.type == WalletType.bitcoin)
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
widget.privacySettingsViewModel.type == WalletType.decred)
Builder(builder: (_) {
final val = testnetValue ?? false;
return SettingsSwitcherCell(
@@ -301,7 +302,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
widget.nodeViewModel.save();
}
if (testnetValue == true) {
if (testnetValue == true &&
widget.privacySettingsViewModel.type ==
WalletType.bitcoin) {
// TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type
// Currently this is so you can create a working testnet wallet but you need to keep switching back the node if you use multiple wallets at once
widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress;
@@ -5,6 +5,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/utils/show_pop_up.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
@@ -15,7 +16,7 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
class NodeCreateOrEditPage extends BasePage {
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected, this.type})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_pathController = TextEditingController(),
@@ -86,6 +87,7 @@ class NodeCreateOrEditPage extends BasePage {
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
final Node? editingNode;
final bool? isSelected;
final WalletType? type;
@override
Widget body(BuildContext context) {
@@ -130,6 +132,7 @@ class NodeCreateOrEditPage extends BasePage {
formKey: _formKey,
nodeViewModel: nodeCreateOrEditViewModel,
editingNode: editingNode,
type: type,
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
+4 -1
View File
@@ -5,6 +5,7 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -15,6 +16,7 @@ class NodeForm extends StatelessWidget {
required this.nodeViewModel,
required this.formKey,
this.editingNode,
this.type,
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
_pathController = TextEditingController(text: editingNode?.path.toString()),
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
@@ -76,6 +78,7 @@ class NodeForm extends StatelessWidget {
final NodeCreateOrEditViewModel nodeViewModel;
final GlobalKey<FormState> formKey;
final Node? editingNode;
final WalletType? type;
final TextEditingController _addressController;
final TextEditingController _pathController;
@@ -96,7 +99,7 @@ class NodeForm extends StatelessWidget {
child: BaseTextFormField(
controller: _addressController,
hintText: S.of(context).node_address,
validator: NodeAddressValidator(),
validator: type == WalletType.decred ? NodeAddressValidatorDecredBlankException() : NodeAddressValidator(),
),
)
],
+54 -17
View File
@@ -9,41 +9,47 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/wallet_type.dart';
class RescanPage extends BasePage {
RescanPage(this._rescanViewModel)
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
@override
String get title =>
_rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan;
String get title => _rescanViewModel.isSilentPaymentsScan
? S.current.silent_payments_scanning
: S.current.rescan;
final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
final RescanViewModel _rescanViewModel;
@override
Widget body(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => FocusScope.of(context).unfocus(),
child: Padding(
Widget child;
if (_rescanViewModel.wallet.type != WalletType.decred) {
child = Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
child:
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Observer(
builder: (_) => BlockchainHeightWidget(
key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
onHeightOrDateEntered: (value) =>
_rescanViewModel.isButtonEnabled = value,
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
isMwebScan: _rescanViewModel.isMwebScan,
doSingleScan: _rescanViewModel.doSingleScan,
hasDatePicker: !_rescanViewModel.isMwebScan,// disable date picker for mweb for now
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
hasDatePicker: !_rescanViewModel
.isMwebScan, // disable date picker for mweb for now
toggleSingleScan: () => _rescanViewModel.doSingleScan =
!_rescanViewModel.doSingleScan,
walletType: _rescanViewModel.wallet.type,
bitcoinMempoolAPIEnabled: _rescanViewModel.isBitcoinMempoolAPIEnabled,
bitcoinMempoolAPIEnabled:
_rescanViewModel.isBitcoinMempoolAPIEnabled,
)),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
isLoading:
_rescanViewModel.state == RescanWalletState.rescaning,
text: S.of(context).rescan,
onPressed: () async {
if (_rescanViewModel.isSilentPaymentsScan) {
@@ -51,7 +57,8 @@ class RescanPage extends BasePage {
}
_rescanViewModel.rescanCurrentWallet(
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
restoreHeight:
_blockchainHeightWidgetKey.currentState!.height);
Navigator.of(context).pop();
},
@@ -60,7 +67,35 @@ class RescanPage extends BasePage {
isDisabled: !_rescanViewModel.isButtonEnabled,
))
]),
),
);
} else {
child = Center(
child: Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Spacer(),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: _rescanViewModel.state ==
RescanWalletState.rescaning,
text: S.of(context).rescan,
onPressed: () async {
await _rescanViewModel.rescanCurrentWallet(
restoreHeight: 0);
Navigator.of(context).pop();
},
color: Theme.of(context).primaryColor,
textColor: Colors.white,
))
]),
));
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => FocusScope.of(context).unfocus(),
child: child,
);
}
@@ -70,14 +105,16 @@ class RescanPage extends BasePage {
Navigator.of(context).pop();
final needsToSwitch =
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) ==
false;
if (needsToSwitch) {
return showPopUp<void>(
context: navigatorKey.currentState!.context,
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
alertTitle: S.of(_dialogContext).change_current_node_title,
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
alertContent:
S.of(_dialogContext).confirm_silent_payments_switch_node,
rightButtonText: S.of(_dialogContext).confirm,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async {
@@ -15,6 +15,7 @@ class WalletRestoreFromKeysForm extends StatefulWidget {
WalletRestoreFromKeysForm({
required this.walletRestoreViewModel,
required this.onPrivateKeyChange,
required this.onViewKeyEntered,
required this.displayPrivateKeyField,
required this.onHeightOrDateEntered,
required this.displayWalletPassword,
@@ -27,6 +28,7 @@ class WalletRestoreFromKeysForm extends StatefulWidget {
final Function(bool) onHeightOrDateEntered;
final WalletRestoreViewModel walletRestoreViewModel;
final void Function(String)? onPrivateKeyChange;
final void Function(bool)? onViewKeyEntered;
final bool displayPrivateKeyField;
final bool displayWalletPassword;
final RestoredWallet? restoredWallet;
@@ -97,6 +99,10 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
blockchainHeightKey.currentState?.restoreHeightController.text = widget.restoredWallet!.height.toString();
}
});
viewKeyController.addListener(() {
widget.onViewKeyEntered?.call(viewKeyController.text.isNotEmpty);
});
}
@override
@@ -187,6 +193,19 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
}
Widget _restoreFromKeysFormFields() {
// Decred can only restore a view only wallet with an account pubkey. Other
// fields are not used.
if (widget.walletRestoreViewModel.type == WalletType.decred) {
return Column(
children: [
BaseTextFormField(
controller: viewKeyController,
hintText: S.of(context).view_key_public,
maxLines: null,
)],
);
}
if (widget.displayPrivateKeyField) {
// the term "private key" isn't actually what we're accepting here, and it's confusing to
// users of the nano community, what this form actually accepts (when importing for nano) is a nano seed in it's hex form, referred to in code as a "seed key"
@@ -168,14 +168,16 @@ class WalletRestorePage extends BasePage {
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} else {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
credentials['spendKey'] =
walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
credentials['height'] =
walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
if (walletRestoreViewModel.type != WalletType.decred) {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['spendKey'] =
walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
credentials['height'] =
walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
}
}
}
@@ -467,6 +469,11 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
widget.walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
}
},
onViewKeyEntered: (bool entered) {
if (walletRestoreViewModel.type == WalletType.decred) {
walletRestoreViewModel.isButtonEnabled = entered;
}
},
onPasswordChange: (String password) =>
widget.walletRestoreViewModel.walletPassword = password,
onRepeatedPasswordChange: (String repeatedPassword) =>
@@ -538,13 +545,19 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
// bip39:
final validBip39SeedLengths = [12, 18, 24];
final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven];
final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred];
// if it's a bip39 wallet and the length is not valid return false
if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) &&
!(validBip39SeedLengths.contains(seedWords.length))) {
return false;
}
if ((walletRestoreViewModel.type == WalletType.decred) &&
seedWords.length !=
WalletRestoreViewModelBase.decredSeedMnemonicLength) {
return false;
}
final words =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
return seedWords.toSet().difference(words).toSet().isEmpty;
@@ -72,6 +72,10 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
late bool showLegacySeedTab;
late bool isLegacySeedOnly;
bool get _hasSeeds =>
widget.walletKeysViewModel.legacySeedSplit.length > 10 ||
widget.walletKeysViewModel.seedSplit.length > 10;
@override
void initState() {
super.initState();
@@ -160,11 +164,10 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
Widget _buildSeedTab(BuildContext context, bool isLegacySeed) {
return Column(
children: [
if (isLegacySeedOnly || isLegacySeed)
...[
_buildHeightBox(),
const SizedBox(height: 20),
],
if (isLegacySeedOnly || isLegacySeed) ...[
_buildHeightBox(),
const SizedBox(height: 20),
],
(_buildPassphraseBox() ?? Container()),
if (widget.walletKeysViewModel.passphrase.isNotEmpty) const SizedBox(height: 20),
Expanded(
@@ -175,13 +178,14 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
),
),
const SizedBox(height: 10),
_buildBottomActionPanel(
titleForClipboard: S.of(context).wallet_seed.toLowerCase(),
dataToCopy: isLegacySeed
? widget.walletKeysViewModel.legacySeed
: widget.walletKeysViewModel.seed,
onShowQR: () async => _showQR(context),
),
if (_hasSeeds)
_buildBottomActionPanel(
titleForClipboard: S.of(context).wallet_seed.toLowerCase(),
dataToCopy: isLegacySeed
? widget.walletKeysViewModel.legacySeed
: widget.walletKeysViewModel.seed,
onShowQR: () async => _showQR(context),
),
],
);
}
@@ -326,7 +330,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
),
);
}
Widget _buildBottomActionPanel({
required String titleForClipboard,
required String dataToCopy,
@@ -122,6 +122,7 @@ class WalletListBodyState extends State<WalletListBody> {
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final decredIcon = Image.asset('assets/images/decred_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);
@@ -136,6 +137,8 @@ class WalletListBodyState extends State<WalletListBody> {
final double tileHeight = 60;
Flushbar<void>? _progressBar;
bool _loadingWallet = false;
@override
Widget build(BuildContext context) {
final newWalletImage = Image.asset('assets/images/new_wallet.png',
@@ -480,6 +483,10 @@ class WalletListBodyState extends State<WalletListBody> {
}
Future<void> _loadWallet(WalletListItem wallet) async {
if (_loadingWallet) return;
_loadingWallet = true;
if (SettingsStoreBase.walletPasswordDirectInput) {
Navigator.of(context).pushNamed(Routes.walletUnlockLoadable,
arguments: WalletUnlockArguments(
@@ -492,13 +499,17 @@ class WalletListBodyState extends State<WalletListBody> {
},
walletName: wallet.name,
walletType: wallet.type));
_loadingWallet = false;
return;
}
await widget.authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) return;
if (!isAuthenticatedSuccessfully) {
_loadingWallet = false;
return;
}
try {
final requireHardwareWalletConnection = widget.walletListViewModel
@@ -555,6 +566,8 @@ class WalletListBodyState extends State<WalletListBody> {
.of(context)
.wallet_list_failed_to_load(wallet.name, e.toString()));
}
} finally {
_loadingWallet = false;
}
},
conditionToDetermineIfToUse2FA:
@@ -9,6 +9,9 @@ import 'package:intl/intl.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cw_core/wallet_type.dart';
class BlockchainHeightWidget extends StatefulWidget {
BlockchainHeightWidget({
@@ -183,7 +186,9 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
bitcoinMempoolAPIEnabled: await widget.bitcoinMempoolAPIEnabled,
);
} else {
if (widget.walletType == WalletType.monero) {
if (widget.walletType == WalletType.decred) {
height = decred!.heightByDate(date);
} else if (widget.walletType == WalletType.monero) {
height = monero!.getHeightByDate(date: date);
} else {
assert(widget.walletType == WalletType.wownero,
+3
View File
@@ -65,6 +65,9 @@ class SeedWidgetState extends State<SeedWidget> {
});
widget.onSeedChange?.call(text);
});
Future.delayed(Duration.zero, () {
widget.onSeedChange?.call(text);
});
}
void changeSeedLanguage(String language) {