feat: xmrrpc (and wowrpc) node uri scanning ui for adding new nodes

This commit is contained in:
julian 2024-12-11 15:26:12 -06:00
parent 4af7243265
commit cdf2dd8819

View file

@ -10,6 +10,7 @@
import 'dart:async';
import 'package:barcode_scan2/barcode_scan2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -26,6 +27,8 @@ import '../../../../utilities/assets.dart';
import '../../../../utilities/constants.dart';
import '../../../../utilities/enums/sync_type_enum.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/logger.dart';
import '../../../../utilities/node_uri_util.dart';
import '../../../../utilities/test_node_connection.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/tor_plain_net_option_enum.dart';
@ -38,7 +41,9 @@ import '../../../../widgets/conditional_parent.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/qr_code_scanner_dialog.dart';
import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/icon_widgets/qrcode_icon.dart';
import '../../../../widgets/icon_widgets/x_icon.dart';
import '../../../../widgets/stack_dialog.dart';
import '../../../../widgets/stack_text_field.dart';
@ -73,6 +78,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
late final String? nodeId;
late final bool isDesktop;
(NodeModel, String)? _scannedResult;
late bool saveEnabled;
late bool testConnectionEnabled;
@ -330,6 +337,77 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
}
}
void _scanQr() async {
if (Util.isDesktop) {
try {
await showDialog<void>(
context: context,
builder: (context) {
return QrCodeScannerDialog(
onQrCodeDetected: (qrCodeData) {
try {
_processQrData(qrCodeData);
} catch (e, s) {
Logging.instance.log(
"Error processing QR code data: $e\n$s",
level: LogLevel.Error,
);
}
},
);
},
);
} catch (e, s) {
Logging.instance.log(
"Error opening QR code scanner dialog: $e\n$s",
level: LogLevel.Error,
);
}
} else {
try {
final result = await BarcodeScanner.scan();
await _processQrData(result.rawContent);
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Warning,
);
}
}
}
Future<void> _processQrData(String data) async {
try {
final nodeQrData = NodeQrUtil.decodeUri(data);
if (mounted) {
setState(() {
_scannedResult = (
NodeModel(
host: nodeQrData.host,
port: nodeQrData.port,
name: nodeQrData.label ?? "",
id: const Uuid().v1(),
useSSL: nodeQrData.scheme == "https",
enabled: true,
coinName: coin.identifier,
isFailover: true,
isDown: false,
torEnabled: true,
clearnetEnabled: !nodeQrData.host.endsWith(".onion"),
loginName: (nodeQrData as LibMoneroNodeQrData?)?.user,
),
(nodeQrData as LibMoneroNodeQrData?)?.password ?? ""
);
});
}
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Warning,
);
}
}
@override
void initState() {
isDesktop = Util.isDesktop;
@ -390,6 +468,35 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
style: STextStyles.navBarTitle(context),
),
actions: [
if (viewType == AddEditNodeViewType.add &&
coin
is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("qrNodeAppBarButtonKey"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.background,
icon: QrCodeIcon(
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
onPressed: _scanQr,
),
),
),
if (viewType == AddEditNodeViewType.edit &&
ref
.watch(
@ -473,19 +580,47 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 8,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(
width: 8,
),
const AppBarBackButton(
iconSize: 24,
size: 40,
),
Text(
"Add new node",
style: STextStyles.desktopH3(context),
Row(
children: [
const SizedBox(
width: 8,
),
const AppBarBackButton(
iconSize: 24,
size: 40,
),
Text(
"Add new node",
style: STextStyles.desktopH3(context),
),
],
),
if (coin
is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future
Padding(
padding: const EdgeInsets.only(right: 32),
child: AppBarIconButton(
size: 40,
color: isDesktop
? Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG
: Theme.of(context)
.extension<StackColors>()!
.background,
icon: const QrCodeIcon(
width: 21,
height: 21,
),
onPressed: _scanQr,
),
),
],
),
Padding(
@ -504,7 +639,9 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
NodeForm(
node: node,
key: Key((node ?? _scannedResult?.$1)?.id ?? "none"),
node: node ?? _scannedResult?.$1,
scannedPw: _scannedResult?.$2,
secureStore: ref.read(secureStoreProvider),
readOnly: false,
coin: widget.coin,
@ -629,6 +766,7 @@ class NodeForm extends ConsumerStatefulWidget {
const NodeForm({
super.key,
this.node,
this.scannedPw,
required this.secureStore,
required this.readOnly,
required this.coin,
@ -636,6 +774,7 @@ class NodeForm extends ConsumerStatefulWidget {
});
final NodeModel? node;
final String? scannedPw;
final SecureStorageInterface secureStore;
final bool readOnly;
final CryptoCurrency coin;
@ -738,13 +877,15 @@ class _NodeFormState extends ConsumerState<NodeForm> {
if (widget.node != null) {
final node = widget.node!;
if (enableAuthFields) {
node.getPassword(widget.secureStore).then((value) {
if (value is String) {
_passwordController.text = value;
}
});
_usernameController.text = node.loginName ?? "";
if (widget.scannedPw == null) {
node.getPassword(widget.secureStore).then((value) {
if (value is String) {
_passwordController.text = value;
}
});
} else {
_passwordController.text = widget.scannedPw!;
}
}
_nameController.text = node.name;