Cw 602 nano bad rep (#1356)

* add support for paths in node settings

* update translations and fixes

* fix node path

* add rep warning flag

* update translations

* code cleanup [skip ci]

* add additional node options

* add migration

* update transaction history rpc to be under the limit

* review fixes [skip ci]

* [skip ci] updates

* move n2_node.dart

* minor code improvements

* more minor code cleanup
This commit is contained in:
Matthew Fosse
2024-04-12 14:36:42 +02:00
committed by GitHub
parent 7abe5735c0
commit fce6394bca
46 changed files with 881 additions and 264 deletions
+5
View File
@@ -8,3 +8,8 @@ class NodeAddressValidator extends TextValidator {
pattern:
'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$|^[0-9a-zA-Z.\-]+\$');
}
class NodePathValidator extends TextValidator {
NodePathValidator()
: super(errorMessage: S.current.error_text_node_address, pattern: '^([/0-9a-zA-Z.\-]+)?\$');
}
+32 -2
View File
@@ -216,6 +216,10 @@ Future<void> defaultSettingsMigration(
await disableServiceStatusFiatDisabled(sharedPreferences);
break;
case 31:
await updateNanoNodeList(nodes: nodes);
break;
default:
break;
}
@@ -230,9 +234,35 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
}
Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoNodes();
var listOfNewEndpoints = <String>[
"app.natrium.io",
"rainstorm.city",
"node.somenano.com",
"nanoslo.0x.no",
"www.bitrequest.app",
];
// add new nodes:
for (final node in nodeList) {
if (listOfNewEndpoints.contains(node.uriRaw)) {
await nodes.add(node);
}
}
// update the nautilus node:
final nautilusNode =
nodes.values.firstWhereOrNull((element) => element.uriRaw == "node.perish.co");
if (nautilusNode != null) {
nautilusNode.uriRaw = "node.nautilus.io";
nautilusNode.path = "/api";
nautilusNode.useSSL = true;
await nautilusNode.save();
}
}
Future<void> disableServiceStatusFiatDisabled(SharedPreferences sharedPreferences) async {
final currentFiat =
await sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? -1;
final currentFiat = await sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? -1;
if (currentFiat == -1 || currentFiat == FiatApiMode.enabled.raw) {
return;
}
+1
View File
@@ -45,6 +45,7 @@ class PreferencesKey {
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowRepWarning = 'should_show_rep_warning';
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
static const syncModeKey = 'sync_mode';
static const syncAllKey = 'sync_all';
+1 -1
View File
@@ -163,7 +163,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 30,
initialMigrationVersion: 31,
);
}
+10
View File
@@ -186,6 +186,16 @@ class CWNano extends Nano {
String getRepresentative(Object wallet) {
return (wallet as NanoWallet).representative;
}
@override
Future<List<N2Node>> getN2Reps(Object wallet) async {
return (wallet as NanoWallet).getN2Reps();
}
@override
bool isRepOk(Object wallet) {
return (wallet as NanoWallet).isRepOk;
}
}
class CWNanoUtil extends NanoUtil {
@@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/src/widgets/introducing_card.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
@@ -183,6 +184,22 @@ class CryptoBalanceWidget extends StatelessWidget {
return Container();
},
),
Observer(builder: (_) {
if (!dashboardViewModel.showRepWarning) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
title: S.current.rep_warning,
subTitle: S.current.rep_warning_sub,
onTap: () => Navigator.of(context).pushNamed(Routes.changeRep),
onClose: () {
dashboardViewModel.settingsStore.shouldShowRepWarning = false;
},
),
);
}),
Observer(
builder: (_) {
return ListView.separated(
+301 -92
View File
@@ -5,10 +5,12 @@ 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/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/address_theme.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/n2_node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -21,9 +23,7 @@ class NanoChangeRepPage extends BasePage {
: _wallet = wallet,
_settingsStore = settingsStore,
_addressController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
_addressController.text = nano!.getRepresentative(wallet);
}
_formKey = GlobalKey<FormState>() {}
final TextEditingController _addressController;
final WalletBase _wallet;
@@ -34,105 +34,314 @@ class NanoChangeRepPage extends BasePage {
@override
String get title => S.current.change_rep;
N2Node getCurrentRepNode(List<N2Node> nodes) {
final currentRepAccount = nano!.getRepresentative(_wallet);
final currentNode = nodes.firstWhere(
(node) => node.account == currentRepAccount,
orElse: () => N2Node(
account: currentRepAccount,
alias: currentRepAccount,
score: 0,
uptime: "???",
weight: 0,
),
);
return currentNode;
}
@override
Widget body(BuildContext context) {
return Form(
key: _formKey,
child: Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: AddressTextField(
controller: _addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
_addressController.text = paymentRequest.address;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
],
buttonColor: Theme.of(context).extension<AddressTheme>()!.actionButtonColor,
validator: AddressValidator(type: CryptoCurrency.nano),
child: FutureBuilder(
future: nano!.getN2Reps(_wallet),
builder: (context, snapshot) {
if (snapshot.data == null) {
return SizedBox();
}
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
topSectionPadding: EdgeInsets.only(bottom: 24),
topSection: Column(
children: [
Row(
children: <Widget>[
Expanded(
child: AddressTextField(
controller: _addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
_addressController.text = paymentRequest.address;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
],
buttonColor:
Theme.of(context).extension<AddressTheme>()!.actionButtonColor,
validator: AddressValidator(type: CryptoCurrency.nano),
),
)
],
),
Column(
children: [
Container(
margin: EdgeInsets.only(top: 12),
child: Text(
S.current.nano_current_rep,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
)
],
_buildSingleRepresentative(
context,
getCurrentRepNode(snapshot.data as List<N2Node>),
isList: false,
),
Divider(height: 20),
Container(
margin: EdgeInsets.only(top: 12),
child: Text(
S.current.nano_pick_new_rep,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
],
),
],
),
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
child: Column(
children: _getRepresentativeWidgets(context, snapshot.data as List<N2Node>),
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () => _onSubmit(context),
text: S.of(context).change,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
)),
],
)),
),
);
},
),
);
}
Future<void> _onSubmit(BuildContext context) async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_rep,
alertContent: S.of(context).change_rep_message,
rightButtonText: S.of(context).change,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
try {
_settingsStore.defaultNanoRep = _addressController.text;
await nano!.changeRep(_wallet, _addressController.text);
// reset this flag whenever we successfully change reps:
_settingsStore.shouldShowRepWarning = true;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).successful,
alertContent: S.of(context).change_rep_successful,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
throw e;
}
}
}
List<Widget> _getRepresentativeWidgets(BuildContext context, List<N2Node>? list) {
if (list == null) {
return [];
}
final List<Widget> ret = [];
for (final N2Node node in list) {
if (node.alias != null && node.alias!.trim().isNotEmpty) {
ret.add(_buildSingleRepresentative(context, node));
}
}
return ret;
}
Widget _buildSingleRepresentative(BuildContext context, N2Node rep, {bool isList = true}) {
return Column(
children: <Widget>[
if (isList)
Divider(
height: 2,
),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: () async {
if (!isList) {
return;
}
_addressController.text = rep.account!;
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsetsDirectional.only(start: 24),
width: MediaQuery.of(context).size.width * 0.50,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_sanitizeAlias(rep.alias),
style: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
Container(
margin: const EdgeInsets.only(top: 7),
child: RichText(
text: TextSpan(
text: "${S.current.voting_weight}: ${rep.weight.toString()}%",
style: TextStyle(
color:
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
fontWeight: FontWeight.w700,
fontSize: 14.0,
),
),
),
),
Container(
margin: const EdgeInsets.only(top: 4),
child: RichText(
text: TextSpan(
text: '',
children: [
TextSpan(
text: "${S.current.uptime}: ",
style: TextStyle(
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
fontWeight: FontWeight.w700,
fontSize: 14,
),
),
TextSpan(
text: rep.uptime,
style: TextStyle(
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
fontWeight: FontWeight.w900,
fontSize: 14,
),
),
],
),
),
),
],
),
),
Container(
margin: const EdgeInsetsDirectional.only(end: 24, start: 14),
child: Stack(
children: <Widget>[
Icon(
Icons.verified,
color: Theme.of(context).primaryColor,
size: 50,
),
Positioned.fill(
child: Container(
margin: EdgeInsets.all(13),
color: Theme.of(context).primaryColor,
),
),
Container(
alignment: const AlignmentDirectional(-0.03, 0.03),
width: 50,
height: 50,
child: Text(
(rep.score).toString(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontSize: 13,
fontWeight: FontWeight.w800,
),
),
),
],
),
),
],
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
return;
}
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_rep,
alertContent: S.of(context).change_rep_message,
rightButtonText: S.of(context).change,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
try {
_settingsStore.defaultNanoRep = _addressController.text;
await nano!.changeRep(_wallet, _addressController.text);
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).successful,
alertContent: S.of(context).change_rep_successful,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
throw e;
}
}
},
text: S.of(context).change,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
)),
],
)),
),
),
],
);
}
String _sanitizeAlias(String? alias) {
if (alias != null) {
return alias.replaceAll(RegExp(r'[^a-zA-Z_.!?_;:-]'), '');
}
return '';
}
}
@@ -18,6 +18,7 @@ class NodeCreateOrEditPage extends BasePage {
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_pathController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
@@ -49,6 +50,8 @@ class NodeCreateOrEditPage extends BasePage {
_addressController.addListener(
() => nodeCreateOrEditViewModel.address = _addressController.text);
_pathController.addListener(
() => nodeCreateOrEditViewModel.path = _pathController.text);
_portController.addListener(
() => nodeCreateOrEditViewModel.port = _portController.text);
_loginController.addListener(
@@ -59,6 +62,7 @@ class NodeCreateOrEditPage extends BasePage {
final GlobalKey<FormState> _formKey;
final TextEditingController _addressController;
final TextEditingController _pathController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
+73 -55
View File
@@ -16,13 +16,15 @@ class NodeForm extends StatelessWidget {
required this.formKey,
this.editingNode,
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
_pathController = TextEditingController(text: editingNode?.path.toString()),
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
_loginController = TextEditingController(text: editingNode?.login),
_passwordController = TextEditingController(text: editingNode?.password),
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress) {
if (editingNode != null) {
nodeViewModel
..setAddress((editingNode!.uri.host.toString()))
..setPath((editingNode!.path.toString()))
..setPort((editingNode!.uri.port.toString()))
..setPassword((editingNode!.password ?? ''))
..setLogin((editingNode!.login ?? ''))
@@ -57,10 +59,12 @@ class NodeForm extends StatelessWidget {
});
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
_pathController.addListener(() => nodeViewModel.path = _pathController.text);
_portController.addListener(() => nodeViewModel.port = _portController.text);
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
_socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
_socksAddressController
.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
}
final NodeCreateOrEditViewModel nodeViewModel;
@@ -68,6 +72,7 @@ class NodeForm extends StatelessWidget {
final Node? editingNode;
final TextEditingController _addressController;
final TextEditingController _pathController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
@@ -91,6 +96,18 @@ class NodeForm extends StatelessWidget {
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _pathController,
hintText: "/path",
validator: NodePathValidator(),
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
@@ -103,6 +120,26 @@ class NodeForm extends StatelessWidget {
],
),
SizedBox(height: 10.0),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.useSSL,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) => nodeViewModel.useSSL = value,
caption: S.of(context).use_ssl,
),
)
],
),
),
SizedBox(height: 10.0),
if (nodeViewModel.hasAuthCredentials) ...[
Row(
children: <Widget>[
@@ -123,25 +160,6 @@ class NodeForm extends StatelessWidget {
))
],
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.useSSL,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) => nodeViewModel.useSSL = value,
caption: S.of(context).use_ssl,
),
)
],
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
@@ -163,44 +181,44 @@ class NodeForm extends StatelessWidget {
),
Observer(
builder: (_) => Column(
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.useSocksProxy,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) {
if (!value) {
_socksAddressController.text = '';
}
nodeViewModel.useSocksProxy = value;
},
caption: 'SOCKS Proxy',
),
],
),
),
if (nodeViewModel.useSocksProxy) ...[
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.useSocksProxy,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) {
if (!value) {
_socksAddressController.text = '';
}
nodeViewModel.useSocksProxy = value;
},
caption: 'SOCKS Proxy',
),
],
),
),
if (nodeViewModel.useSocksProxy) ...[
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _socksAddressController,
hintText: '[<ip>:]<port>',
validator: SocksProxyNodeAddressValidator(),
))
],
),
]
],
)),
],
),
]
],
)),
]
],
),
+33 -25
View File
@@ -4,15 +4,15 @@ import 'package:flutter/material.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
class DashBoardRoundedCardWidget extends StatelessWidget {
DashBoardRoundedCardWidget({
required this.onTap,
required this.title,
required this.subTitle,
this.onClose,
});
final VoidCallback onTap;
final VoidCallback? onClose;
final String title;
final String subTitle;
@@ -26,7 +26,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
child: Stack(
children: [
Container(
padding: EdgeInsets.all(20),
padding: EdgeInsets.fromLTRB(20, 20, 40, 20),
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
@@ -35,32 +35,40 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
),
),
child:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontSize: 24,
fontWeight: FontWeight.w900,
),
),
SizedBox(height: 5),
Text(
subTitle,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato'),
)
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontSize: 24,
fontWeight: FontWeight.w900,
),
),
SizedBox(height: 5),
Text(
subTitle,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato'),
)
],
),
),
if (onClose != null)
Positioned(
top: 10,
right: 10,
child: IconButton(
icon: Icon(Icons.close),
onPressed: onClose,
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
),
),
],
),
);
}
}
@@ -2,16 +2,21 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ScrollableWithBottomSection extends StatefulWidget {
ScrollableWithBottomSection(
{required this.content,
required this.bottomSection,
this.contentPadding,
this.bottomSectionPadding});
ScrollableWithBottomSection({
required this.content,
required this.bottomSection,
this.topSection,
this.contentPadding,
this.bottomSectionPadding,
this.topSectionPadding,
});
final Widget content;
final Widget bottomSection;
final Widget? topSection;
final EdgeInsets? contentPadding;
final EdgeInsets? bottomSectionPadding;
final EdgeInsets? topSectionPadding;
@override
ScrollableWithBottomSectionState createState() => ScrollableWithBottomSectionState();
@@ -22,6 +27,12 @@ class ScrollableWithBottomSectionState extends State<ScrollableWithBottomSection
Widget build(BuildContext context) {
return Column(
children: [
if (widget.topSection != null)
Padding(
padding: widget.topSectionPadding?.copyWith(top: 10) ??
EdgeInsets.only(top: 10, bottom: 20, right: 20, left: 20),
child: widget.topSection,
),
Expanded(
child: SingleChildScrollView(
child: Padding(
+82 -69
View File
@@ -79,6 +79,7 @@ abstract class SettingsStoreBase with Store {
required Map<WalletType, Node> nodes,
required Map<WalletType, Node> powNodes,
required this.shouldShowYatPopup,
required this.shouldShowRepWarning,
required this.isBitcoinBuyEnabled,
required this.actionlistDisplayMode,
required this.pinTimeOutDuration,
@@ -225,6 +226,9 @@ abstract class SettingsStoreBase with Store {
(bool shouldShowYatPopup) =>
sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
reaction((_) => shouldShowRepWarning,
(bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val));
defaultBuyProviders.observe((change) {
final String key = 'buyProvider_${change.key.toString()}';
if (change.newValue != null) {
@@ -536,6 +540,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool shouldShowYatPopup;
@observable
bool shouldShowRepWarning;
@observable
bool shouldShowMarketPlaceInDashboard;
@@ -878,6 +885,8 @@ abstract class SettingsStoreBase with Store {
final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
final shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? true;
final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
@@ -1034,75 +1043,77 @@ abstract class SettingsStoreBase with Store {
'';
return SettingsStore(
secureStorage: secureStorage,
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
initialFiatCurrency: currentFiatCurrency,
initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialMoneroSeedType: moneroSeedType,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
initialDisableBulletin: disableBulletin,
initialWalletListOrder: walletListOrder,
initialWalletListAscending: walletListAscending,
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset,
initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
seedPhraseLength: seedPhraseWordCount,
initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
defaultNanoRep: defaultNanoRep,
defaultBananoRep: defaultBananoRep,
lookupsTwitter: lookupsTwitter,
lookupsMastodon: lookupsMastodon,
lookupsYatService: lookupsYatService,
lookupsUnstoppableDomains: lookupsUnstoppableDomains,
lookupsOpenAlias: lookupsOpenAlias,
lookupsENS: lookupsENS,
customBitcoinFeeRate: customBitcoinFeeRate,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority,
initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
initialShouldRequireTOTP2FAForSendsToInternalWallets:
shouldRequireTOTP2FAForSendsToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
shouldRequireTOTP2FAForExchangesToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToExternalWallets:
shouldRequireTOTP2FAForExchangesToExternalWallets,
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
initialPolygonTransactionPriority: polygonTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup);
secureStorage: secureStorage,
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
initialFiatCurrency: currentFiatCurrency,
initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialMoneroSeedType: moneroSeedType,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
initialDisableBulletin: disableBulletin,
initialWalletListOrder: walletListOrder,
initialWalletListAscending: walletListAscending,
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset,
initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
seedPhraseLength: seedPhraseWordCount,
initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
defaultNanoRep: defaultNanoRep,
defaultBananoRep: defaultBananoRep,
lookupsTwitter: lookupsTwitter,
lookupsMastodon: lookupsMastodon,
lookupsYatService: lookupsYatService,
lookupsUnstoppableDomains: lookupsUnstoppableDomains,
lookupsOpenAlias: lookupsOpenAlias,
lookupsENS: lookupsENS,
customBitcoinFeeRate: customBitcoinFeeRate,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority,
initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
initialShouldRequireTOTP2FAForSendsToInternalWallets:
shouldRequireTOTP2FAForSendsToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
shouldRequireTOTP2FAForExchangesToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToExternalWallets:
shouldRequireTOTP2FAForExchangesToExternalWallets,
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
initialPolygonTransactionPriority: polygonTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup,
shouldShowRepWarning: shouldShowRepWarning,
);
}
Future<void> reload({required Box<Node> nodeSource}) async {
@@ -1198,6 +1209,8 @@ abstract class SettingsStoreBase with Store {
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
shouldShowYatPopup =
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? shouldShowRepWarning;
sortBalanceBy = SortBalanceBy
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
@@ -11,6 +11,7 @@ import 'package:cake_wallet/entities/service_status.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/orders_store.dart';
@@ -370,6 +371,18 @@ abstract class DashboardViewModelBase with Store {
@computed
bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
bool get showRepWarning {
if (wallet.type != WalletType.nano) {
return false;
}
if (!settingsStore.shouldShowRepWarning) {
return false;
}
return !nano!.isRepOk(wallet);
}
Future<void> reconnect() async {
final node = appStore.settingsStore.getCurrentNode(wallet.type);
await wallet.connectToNode(node: node);
@@ -12,16 +12,15 @@ import 'package:permission_handler/permission_handler.dart';
part 'node_create_or_edit_view_model.g.dart';
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
with _$NodeCreateOrEditViewModel;
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase with _$NodeCreateOrEditViewModel;
abstract class NodeCreateOrEditViewModelBase with Store {
NodeCreateOrEditViewModelBase(
this._nodeSource, this._walletType, this._settingsStore)
NodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore)
: state = InitialExecutionState(),
connectionState = InitialExecutionState(),
useSSL = false,
address = '',
path = '',
port = '',
login = '',
password = '',
@@ -35,6 +34,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@observable
String address;
@observable
String path;
@observable
String port;
@@ -84,6 +86,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@action
void reset() {
address = '';
path = '';
port = '';
login = '';
password = '';
@@ -99,6 +102,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@action
void setAddress(String val) => address = val;
@action
void setPath(String val) => path = val;
@action
void setLogin(String val) => login = val;
@@ -121,6 +127,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
final node = Node(
uri: uri,
path: path,
type: _walletType,
login: login,
password: password,
@@ -151,6 +158,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
Future<void> connect() async {
final node = Node(
uri: uri,
path: path,
type: _walletType,
login: login,
password: password,
@@ -183,7 +191,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
Future<void> scanQRCodeForNewNode(BuildContext context) async {
try {
bool isCameraPermissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
await PermissionHandler.checkPermission(Permission.camera, context);
if (!isCameraPermissionGranted) return;
String code = await presentQRScanner();
@@ -198,7 +206,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
}
final userInfo = uri.userInfo.split(':');
if (userInfo.length < 2) {
throw Exception('Unexpected scan QR code value: Value is invalid');
}
@@ -207,8 +215,11 @@ abstract class NodeCreateOrEditViewModelBase with Store {
final rpcPassword = userInfo[1];
final ipAddress = uri.host;
final port = uri.port.toString();
final path = uri.path;
setAddress(ipAddress);
setPath(path);
setPassword(rpcPassword);
setLogin(rpcUser);
setPort(port);