mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-19 05:58:50 +02:00
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:
co-authored by
JoeGruffins
Wisdom Arerosuoghene
Philemon Ukane
Czarek Nakamoto
parent
52a39e29d4
commit
0ba54fa602
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user