mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-03 01:09:22 +00:00
Ensure plain addresses are parsed from qr codes. Use uri parsing everywhere with a couple small tweaks.
This commit is contained in:
parent
f15d051108
commit
4594801cf3
12 changed files with 425 additions and 630 deletions
|
@ -66,6 +66,62 @@ class _NewContactAddressEntryFormState
|
|||
|
||||
List<CryptoCurrency> coins = [];
|
||||
|
||||
void _onQrTapped() async {
|
||||
try {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = false;
|
||||
final qrResult = await widget.barcodeScanner.scan();
|
||||
|
||||
// Future<void>.delayed(
|
||||
// const Duration(seconds: 2),
|
||||
// () => ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true,
|
||||
// );
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
addressController.text = paymentData.address;
|
||||
ref.read(addressEntryDataProvider(widget.id)).address =
|
||||
addressController.text.isEmpty ? null : addressController.text;
|
||||
|
||||
addressLabelController.text =
|
||||
paymentData.label ?? addressLabelController.text;
|
||||
ref.read(addressEntryDataProvider(widget.id)).addressLabel =
|
||||
addressLabelController.text.isEmpty
|
||||
? null
|
||||
: addressLabelController.text;
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref.read(addressEntryDataProvider(widget.id)).coin != null) {
|
||||
if (ref.read(addressEntryDataProvider(widget.id)).coin!.validateAddress(
|
||||
qrResult.rawContent,
|
||||
)) {
|
||||
addressController.text = qrResult.rawContent;
|
||||
ref.read(addressEntryDataProvider(widget.id)).address =
|
||||
qrResult.rawContent;
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true;
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions to scan address qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
addressLabelController = TextEditingController()
|
||||
|
@ -404,71 +460,7 @@ class _NewContactAddressEntryFormState
|
|||
null)
|
||||
TextFieldIconButton(
|
||||
key: const Key("addAddressBookEntryScanQrButtonKey"),
|
||||
onTap: () async {
|
||||
try {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = false;
|
||||
final qrResult = await widget.barcodeScanner.scan();
|
||||
|
||||
// Future<void>.delayed(
|
||||
// const Duration(seconds: 2),
|
||||
// () => ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true,
|
||||
// );
|
||||
|
||||
final results =
|
||||
AddressUtils.parseUri(qrResult.rawContent);
|
||||
if (results.isNotEmpty) {
|
||||
addressController.text = results["address"] ?? "";
|
||||
ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.address =
|
||||
addressController.text.isEmpty
|
||||
? null
|
||||
: addressController.text;
|
||||
|
||||
addressLabelController.text = results["label"] ??
|
||||
addressLabelController.text;
|
||||
ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.addressLabel =
|
||||
addressLabelController.text.isEmpty
|
||||
? null
|
||||
: addressLabelController.text;
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.coin !=
|
||||
null) {
|
||||
if (ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.coin!
|
||||
.validateAddress(
|
||||
qrResult.rawContent,
|
||||
)) {
|
||||
addressController.text = qrResult.rawContent;
|
||||
ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.address = qrResult.rawContent;
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true;
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions to scan address qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
const SizedBox(
|
||||
|
|
|
@ -713,6 +713,60 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
}
|
||||
|
||||
void _onQrTapped() async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $paymentData",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
// auto fill address
|
||||
_address = paymentData.address;
|
||||
_receiveAddressController.text = _address!;
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = _receiveAddressController.text.isNotEmpty;
|
||||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else {
|
||||
_address = qrResult.rawContent;
|
||||
_receiveAddressController.text = _address ?? "";
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = _receiveAddressController.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
// here we ignore the exception caused by not giving permission
|
||||
// to use the camera to scan a qr code
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_receiveAddressController = TextEditingController();
|
||||
|
@ -1375,63 +1429,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
!isDesktop)
|
||||
TextFieldIconButton(
|
||||
key: const Key("buyViewScanQrButtonKey"),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final results = AddressUtils.parseUri(
|
||||
qrResult.rawContent,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $results",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (results.isNotEmpty) {
|
||||
// auto fill address
|
||||
_address = results["address"] ?? "";
|
||||
_receiveAddressController.text = _address!;
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag =
|
||||
_receiveAddressController
|
||||
.text.isNotEmpty;
|
||||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else {
|
||||
_address = qrResult.rawContent;
|
||||
_receiveAddressController.text =
|
||||
_address ?? "";
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag =
|
||||
_receiveAddressController
|
||||
.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
// here we ignore the exception caused by not giving permission
|
||||
// to use the camera to scan a qr code
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -70,6 +70,78 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
|
||||
bool enableNext = false;
|
||||
|
||||
void _onRefundQrTapped() async {
|
||||
try {
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
// auto fill address
|
||||
_refundController.text = paymentData.address;
|
||||
model.refundAddress = _refundController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController.text.isNotEmpty &&
|
||||
_refundController.text.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
_refundController.text = qrResult.rawContent;
|
||||
model.refundAddress = _refundController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController.text.isNotEmpty &&
|
||||
_refundController.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onToQrTapped() async {
|
||||
try {
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
// auto fill address
|
||||
_toController.text = paymentData.address;
|
||||
model.recipientAddress = _toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController.text.isNotEmpty &&
|
||||
(_refundController.text.isNotEmpty ||
|
||||
!ref.read(efExchangeProvider).supportsRefundAddress);
|
||||
});
|
||||
} else {
|
||||
_toController.text = qrResult.rawContent;
|
||||
model.recipientAddress = _toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController.text.isNotEmpty &&
|
||||
(_refundController.text.isNotEmpty ||
|
||||
!!ref.read(efExchangeProvider).supportsRefundAddress);
|
||||
});
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
model = widget.model;
|
||||
|
@ -137,7 +209,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
@ -405,50 +477,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
key: const Key(
|
||||
"sendViewScanQrButtonKey",
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
final qrResult =
|
||||
await scanner.scan();
|
||||
|
||||
final results =
|
||||
AddressUtils.parseUri(
|
||||
qrResult.rawContent,
|
||||
);
|
||||
if (results.isNotEmpty) {
|
||||
// auto fill address
|
||||
_toController.text =
|
||||
results["address"] ?? "";
|
||||
model.recipientAddress =
|
||||
_toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController
|
||||
.text.isNotEmpty &&
|
||||
(_refundController.text
|
||||
.isNotEmpty ||
|
||||
!supportsRefund);
|
||||
});
|
||||
} else {
|
||||
_toController.text =
|
||||
qrResult.rawContent;
|
||||
model.recipientAddress =
|
||||
_toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController
|
||||
.text.isNotEmpty &&
|
||||
(_refundController.text
|
||||
.isNotEmpty ||
|
||||
!supportsRefund);
|
||||
});
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onToQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
@ -685,51 +714,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
key: const Key(
|
||||
"sendViewScanQrButtonKey",
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
final qrResult =
|
||||
await scanner.scan();
|
||||
|
||||
final results =
|
||||
AddressUtils.parseUri(
|
||||
qrResult.rawContent,
|
||||
);
|
||||
if (results.isNotEmpty) {
|
||||
// auto fill address
|
||||
_refundController.text =
|
||||
results["address"] ??
|
||||
"";
|
||||
model.refundAddress =
|
||||
_refundController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController
|
||||
.text
|
||||
.isNotEmpty &&
|
||||
_refundController
|
||||
.text.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
_refundController.text =
|
||||
qrResult.rawContent;
|
||||
model.refundAddress =
|
||||
_refundController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController
|
||||
.text
|
||||
.isNotEmpty &&
|
||||
_refundController
|
||||
.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onRefundQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -97,7 +97,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
initialDirectory: dir.path,
|
||||
);
|
||||
|
||||
if (path != null) {
|
||||
if (path != null && mounted) {
|
||||
final file = File(path);
|
||||
if (file.existsSync()) {
|
||||
unawaited(
|
||||
|
@ -109,6 +109,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
);
|
||||
} else {
|
||||
await file.writeAsBytes(pngBytes);
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
|
@ -118,6 +119,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// await DocumentFileSavePlus.saveFile(
|
||||
// pngBytes,
|
||||
|
@ -167,7 +169,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
final Map<String, String> queryParams = {};
|
||||
|
||||
if (amountString.isNotEmpty) {
|
||||
queryParams["amount"] = amountString;
|
||||
queryParams["amount"] = amount.toString();
|
||||
}
|
||||
if (noteString.isNotEmpty) {
|
||||
queryParams["message"] = noteString;
|
||||
|
@ -311,7 +313,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 70));
|
||||
}
|
||||
if (mounted) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -120,6 +120,69 @@ class _RecipientState extends ConsumerState<Recipient> {
|
|||
}
|
||||
}
|
||||
|
||||
void _onQrTapped() async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult = await ref.read(pBarcodeScanner).scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $paymentData",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == widget.coin.uriScheme) {
|
||||
// auto fill address
|
||||
|
||||
addressController.text = paymentData.address.trim();
|
||||
|
||||
// autofill amount field
|
||||
if (paymentData.amount != null) {
|
||||
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||
fractionDigits: widget.coin.fractionDigits,
|
||||
);
|
||||
amountController.text =
|
||||
ref.read(pAmountFormatter(widget.coin)).format(
|
||||
amount,
|
||||
withUnitName: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addressController.text = qrResult.rawContent.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_addressIsEmpty = addressController.text.isEmpty;
|
||||
});
|
||||
|
||||
_updateRecipientData();
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while "
|
||||
"trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
addressController = TextEditingController();
|
||||
|
@ -289,76 +352,7 @@ class _RecipientState extends ConsumerState<Recipient> {
|
|||
key: const Key(
|
||||
"sendViewScanQrButtonKey",
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult =
|
||||
await ref.read(pBarcodeScanner).scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
/// TODO: deal with address utils
|
||||
final results =
|
||||
AddressUtils.parseUri(qrResult.rawContent);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $results",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (results.isNotEmpty &&
|
||||
results["scheme"] ==
|
||||
widget.coin.uriScheme) {
|
||||
// auto fill address
|
||||
|
||||
addressController.text =
|
||||
(results["address"] ?? "").trim();
|
||||
|
||||
// autofill amount field
|
||||
if (results["amount"] != null) {
|
||||
final Amount amount =
|
||||
Decimal.parse(results["amount"]!)
|
||||
.toAmount(
|
||||
fractionDigits:
|
||||
widget.coin.fractionDigits,
|
||||
);
|
||||
amountController.text = ref
|
||||
.read(pAmountFormatter(widget.coin))
|
||||
.format(
|
||||
amount,
|
||||
withUnitName: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addressController.text =
|
||||
qrResult.rawContent.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_addressIsEmpty =
|
||||
addressController.text.isEmpty;
|
||||
});
|
||||
|
||||
_updateRecipientData();
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while "
|
||||
"trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -163,9 +163,13 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(qrResult.rawContent);
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = paymentData.address.trim();
|
||||
sendToController.text = _address!;
|
||||
|
@ -195,12 +199,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent.trim();
|
||||
} else {
|
||||
_address = qrResult.rawContent.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
|
|
|
@ -163,25 +163,30 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final results = AddressUtils.parseUri(qrResult.rawContent);
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
|
||||
Logging.instance
|
||||
.log("qrResult parsed: $paymentData", level: LogLevel.Info);
|
||||
|
||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = (results["address"] ?? "").trim();
|
||||
_address = paymentData.address.trim();
|
||||
sendToController.text = _address!;
|
||||
|
||||
// autofill notes field
|
||||
if (results["message"] != null) {
|
||||
noteController.text = results["message"]!;
|
||||
} else if (results["label"] != null) {
|
||||
noteController.text = results["label"]!;
|
||||
if (paymentData.message != null) {
|
||||
noteController.text = paymentData.message!;
|
||||
} else if (paymentData.label != null) {
|
||||
noteController.text = paymentData.label!;
|
||||
}
|
||||
|
||||
// autofill amount field
|
||||
if (results["amount"] != null) {
|
||||
final Amount amount = Decimal.parse(results["amount"]!).toAmount(
|
||||
if (paymentData.amount != null) {
|
||||
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||
fractionDigits: tokenContract.decimals,
|
||||
);
|
||||
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
||||
|
@ -198,12 +203,8 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent.trim();
|
||||
} else {
|
||||
_address = qrResult.rawContent.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
|
@ -145,7 +145,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
|
||||
Future<void> scanWebcam() async {
|
||||
try {
|
||||
await showDialog(
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return QrCodeScannerDialog(
|
||||
|
@ -153,16 +153,20 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
try {
|
||||
_processQrCodeData(qrCodeData);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Error processing QR code data: $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
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);
|
||||
Logging.instance.log(
|
||||
"Error opening QR code scanner dialog: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -655,84 +659,15 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
// return null;
|
||||
// }
|
||||
|
||||
Future<void> scanQr() async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final results = AddressUtils.parseUri(qrResult.rawContent);
|
||||
|
||||
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
|
||||
|
||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = results["address"] ?? "";
|
||||
sendToController.text = _address!;
|
||||
|
||||
// autofill notes field
|
||||
if (results["message"] != null) {
|
||||
_note = results["message"]!;
|
||||
} else if (results["label"] != null) {
|
||||
_note = results["label"]!;
|
||||
}
|
||||
|
||||
// autofill amount field
|
||||
if (results["amount"] != null) {
|
||||
final amount = Decimal.parse(results["amount"]!).toAmount(
|
||||
fractionDigits: coin.fractionDigits,
|
||||
);
|
||||
cryptoAmountController.text = ref
|
||||
.read(pAmountFormatter(coin))
|
||||
.format(amount, withUnitName: false);
|
||||
ref.read(pSendAmount.notifier).state = amount;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent;
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
// here we ignore the exception caused by not giving permission
|
||||
// to use the camera to scan a qr code
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _processQrCodeData(String qrCodeData) {
|
||||
try {
|
||||
final paymentData = AddressUtils.parsePaymentUri(qrCodeData);
|
||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrCodeData,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// Auto fill address.
|
||||
_address = paymentData.address.trim();
|
||||
sendToController.text = _address!;
|
||||
|
@ -756,6 +691,14 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
_note = paymentData.label;
|
||||
}
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
_address = qrCodeData.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
|
@ -807,8 +750,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
}
|
||||
|
||||
try {
|
||||
final paymentData = AddressUtils.parsePaymentUri(content);
|
||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
content,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = paymentData.address;
|
||||
sendToController.text = _address!;
|
||||
|
@ -837,6 +784,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
content = content.split("\n").first.trim();
|
||||
if (coin is Epiccash) {
|
||||
content = AddressUtils().formatAddress(content);
|
||||
}
|
||||
|
|
|
@ -14,14 +14,12 @@ import 'package:decimal/decimal.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../models/isar/models/contact_entry.dart';
|
||||
import '../../../../models/paynym/paynym_account_lite.dart';
|
||||
import '../../../../models/send_view_auto_fill_data.dart';
|
||||
import '../../../../pages/send_view/confirm_transaction_view.dart';
|
||||
import '../../../../pages/send_view/sub_widgets/building_transaction_dialog.dart';
|
||||
import '../../../desktop_home_view.dart';
|
||||
import 'address_book_address_chooser/address_book_address_chooser.dart';
|
||||
import 'desktop_fee_dropdown.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../providers/ui/fee_rate_type_state_provider.dart';
|
||||
import '../../../../providers/ui/preview_tx_button_state_provider.dart';
|
||||
|
@ -51,6 +49,9 @@ import '../../../../widgets/icon_widgets/clipboard_icon.dart';
|
|||
import '../../../../widgets/icon_widgets/x_icon.dart';
|
||||
import '../../../../widgets/stack_text_field.dart';
|
||||
import '../../../../widgets/textfield_icon_button.dart';
|
||||
import '../../../desktop_home_view.dart';
|
||||
import 'address_book_address_chooser/address_book_address_chooser.dart';
|
||||
import 'desktop_fee_dropdown.dart';
|
||||
|
||||
// const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
|
||||
|
||||
|
@ -480,25 +481,30 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final results = AddressUtils.parseUri(qrResult.rawContent);
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
|
||||
Logging.instance
|
||||
.log("qrResult parsed: $paymentData", level: LogLevel.Info);
|
||||
|
||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = results["address"] ?? "";
|
||||
_address = paymentData.address.trim();
|
||||
sendToController.text = _address!;
|
||||
|
||||
// autofill notes field
|
||||
if (results["message"] != null) {
|
||||
_note = results["message"]!;
|
||||
} else if (results["label"] != null) {
|
||||
_note = results["label"]!;
|
||||
if (paymentData.message != null) {
|
||||
_note = paymentData.message!;
|
||||
} else if (paymentData.label != null) {
|
||||
_note = paymentData.label!;
|
||||
}
|
||||
|
||||
// autofill amount field
|
||||
if (results["amount"] != null) {
|
||||
final amount = Decimal.parse(results["amount"]!).toAmount(
|
||||
if (paymentData.amount != null) {
|
||||
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||
fractionDigits:
|
||||
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
|
||||
);
|
||||
|
@ -516,12 +522,8 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
|
|||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent;
|
||||
} else {
|
||||
_address = qrResult.rawContent.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'dart:convert';
|
|||
|
||||
import '../app_config.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'logger.dart';
|
||||
|
||||
class AddressUtils {
|
||||
static final Set<String> recognizedParams = {
|
||||
|
@ -29,166 +30,16 @@ class AddressUtils {
|
|||
return '${address.substring(0, 5)}...${address.substring(address.length - 5)}';
|
||||
}
|
||||
|
||||
// static bool validateAddress(String address, Coin coin) {
|
||||
// //This calls the validate address for each crypto coin, validateAddress is
|
||||
// //only used in 2 places, so I just replaced the old functionality here
|
||||
// switch (coin) {
|
||||
// case Coin.bitcoin:
|
||||
// return Bitcoin(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.bitcoinFrost:
|
||||
// return BitcoinFrost(CryptoCurrencyNetwork.main)
|
||||
// .validateAddress(address);
|
||||
// case Coin.litecoin:
|
||||
// return Litecoin(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.bitcoincash:
|
||||
// return Bitcoincash(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.dogecoin:
|
||||
// return Dogecoin(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.epicCash:
|
||||
// return Epiccash(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.ethereum:
|
||||
// return Ethereum(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.firo:
|
||||
// return Firo(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.eCash:
|
||||
// return Ecash(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.monero:
|
||||
// return Monero(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.wownero:
|
||||
// return Wownero(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.namecoin:
|
||||
// return Namecoin(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.particl:
|
||||
// return Particl(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.peercoin:
|
||||
// return Peercoin(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.solana:
|
||||
// return Solana(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.stellar:
|
||||
// return Stellar(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.nano:
|
||||
// return Nano(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.banano:
|
||||
// return Banano(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.tezos:
|
||||
// return Tezos(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
// case Coin.bitcoinTestNet:
|
||||
// return Bitcoin(CryptoCurrencyNetwork.test).validateAddress(address);
|
||||
// case Coin.bitcoinFrostTestNet:
|
||||
// return BitcoinFrost(CryptoCurrencyNetwork.test)
|
||||
// .validateAddress(address);
|
||||
// case Coin.litecoinTestNet:
|
||||
// return Litecoin(CryptoCurrencyNetwork.test).validateAddress(address);
|
||||
// case Coin.bitcoincashTestnet:
|
||||
// return Bitcoincash(CryptoCurrencyNetwork.test).validateAddress(address);
|
||||
// case Coin.firoTestNet:
|
||||
// return Firo(CryptoCurrencyNetwork.test).validateAddress(address);
|
||||
// case Coin.dogecoinTestNet:
|
||||
// return Dogecoin(CryptoCurrencyNetwork.test).validateAddress(address);
|
||||
// case Coin.peercoinTestNet:
|
||||
// return Peercoin(CryptoCurrencyNetwork.test).validateAddress(address);
|
||||
// case Coin.stellarTestnet:
|
||||
// return Stellar(CryptoCurrencyNetwork.test).validateAddress(address);
|
||||
// }
|
||||
// // throw Exception("moved");
|
||||
// // switch (coin) {
|
||||
// // case Coin.bitcoin:
|
||||
// // return Address.validateAddress(address, bitcoin);
|
||||
// // case Coin.litecoin:
|
||||
// // return Address.validateAddress(address, litecoin);
|
||||
// // case Coin.bitcoincash:
|
||||
// // try {
|
||||
// // // 0 for bitcoincash: address scheme, 1 for legacy address
|
||||
// // final format = bitbox.Address.detectFormat(address);
|
||||
// //
|
||||
// // if (coin == Coin.bitcoincashTestnet) {
|
||||
// // return true;
|
||||
// // }
|
||||
// //
|
||||
// // if (format == bitbox.Address.formatCashAddr) {
|
||||
// // String addr = address;
|
||||
// // if (addr.contains(":")) {
|
||||
// // addr = addr.split(":").last;
|
||||
// // }
|
||||
// //
|
||||
// // return addr.startsWith("q");
|
||||
// // } else {
|
||||
// // return address.startsWith("1");
|
||||
// // }
|
||||
// // } catch (e) {
|
||||
// // return false;
|
||||
// // }
|
||||
// // case Coin.dogecoin:
|
||||
// // return Address.validateAddress(address, dogecoin);
|
||||
// // case Coin.epicCash:
|
||||
// // return validateSendAddress(address) == "1";
|
||||
// // case Coin.ethereum:
|
||||
// // return true; //TODO - validate ETH address
|
||||
// // case Coin.firo:
|
||||
// // return Address.validateAddress(address, firoNetwork);
|
||||
// // case Coin.eCash:
|
||||
// // return Address.validateAddress(address, eCashNetwork);
|
||||
// // case Coin.monero:
|
||||
// // return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) ||
|
||||
// // RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
|
||||
// // case Coin.wownero:
|
||||
// // return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) ||
|
||||
// // RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
|
||||
// // case Coin.namecoin:
|
||||
// // return Address.validateAddress(address, namecoin, namecoin.bech32!);
|
||||
// // case Coin.particl:
|
||||
// // return Address.validateAddress(address, particl);
|
||||
// // case Coin.stellar:
|
||||
// // return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address);
|
||||
// // case Coin.nano:
|
||||
// // return NanoAccounts.isValid(NanoAccountType.NANO, address);
|
||||
// // case Coin.banano:
|
||||
// // return NanoAccounts.isValid(NanoAccountType.BANANO, address);
|
||||
// // case Coin.tezos:
|
||||
// // return RegExp(r"^tz[1-9A-HJ-NP-Za-km-z]{34}$").hasMatch(address);
|
||||
// // case Coin.bitcoinTestNet:
|
||||
// // return Address.validateAddress(address, testnet);
|
||||
// // case Coin.litecoinTestNet:
|
||||
// // return Address.validateAddress(address, litecointestnet);
|
||||
// // case Coin.bitcoincashTestnet:
|
||||
// // try {
|
||||
// // // 0 for bitcoincash: address scheme, 1 for legacy address
|
||||
// // final format = bitbox.Address.detectFormat(address);
|
||||
// //
|
||||
// // if (coin == Coin.bitcoincashTestnet) {
|
||||
// // return true;
|
||||
// // }
|
||||
// //
|
||||
// // if (format == bitbox.Address.formatCashAddr) {
|
||||
// // String addr = address;
|
||||
// // if (addr.contains(":")) {
|
||||
// // addr = addr.split(":").last;
|
||||
// // }
|
||||
// //
|
||||
// // return addr.startsWith("q");
|
||||
// // } else {
|
||||
// // return address.startsWith("1");
|
||||
// // }
|
||||
// // } catch (e) {
|
||||
// // return false;
|
||||
// // }
|
||||
// // case Coin.firoTestNet:
|
||||
// // return Address.validateAddress(address, firoTestNetwork);
|
||||
// // case Coin.dogecoinTestNet:
|
||||
// // return Address.validateAddress(address, dogecointestnet);
|
||||
// // case Coin.stellarTestnet:
|
||||
// // return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address);
|
||||
// // }
|
||||
// }
|
||||
|
||||
/// Return only recognized parameters.
|
||||
static Map<String, String> filterParams(Map<String, String> params) {
|
||||
return Map.fromEntries(params.entries
|
||||
.where((entry) => recognizedParams.contains(entry.key.toLowerCase())));
|
||||
static Map<String, String> _filterParams(Map<String, String> params) {
|
||||
return Map.fromEntries(
|
||||
params.entries
|
||||
.where((entry) => recognizedParams.contains(entry.key.toLowerCase())),
|
||||
);
|
||||
}
|
||||
|
||||
/// Parses a URI string and returns a map with parsed components.
|
||||
static Map<String, String> parseUri(String uri) {
|
||||
static Map<String, String> _parseUri(String uri) {
|
||||
final Map<String, String> result = {};
|
||||
try {
|
||||
final u = Uri.parse(uri);
|
||||
|
@ -273,28 +124,36 @@ class AddressUtils {
|
|||
}
|
||||
|
||||
/// Centralized method to handle various cryptocurrency URIs and return a common object.
|
||||
static PaymentUriData parsePaymentUri(String uri) {
|
||||
final Map<String, String> parsedData = parseUri(uri);
|
||||
///
|
||||
/// Returns null on failure to parse
|
||||
static PaymentUriData? parsePaymentUri(
|
||||
String uri, {
|
||||
Logging? logging,
|
||||
}) {
|
||||
try {
|
||||
final Map<String, String> parsedData = _parseUri(uri);
|
||||
|
||||
// Normalize the URI scheme.
|
||||
final String scheme = parsedData['scheme'] ?? '';
|
||||
parsedData.remove('scheme');
|
||||
|
||||
// Determine the coin type based on the URI scheme.
|
||||
final CryptoCurrency coin = _getCryptoCurrencyByScheme(scheme);
|
||||
|
||||
// Filter out unrecognized parameters.
|
||||
final filteredParams = filterParams(parsedData);
|
||||
final filteredParams = _filterParams(parsedData);
|
||||
|
||||
return PaymentUriData(
|
||||
coin: coin,
|
||||
address: parsedData['address'] ?? '',
|
||||
scheme: scheme,
|
||||
address: parsedData['address']!.trim(),
|
||||
amount: filteredParams['amount'] ?? filteredParams['tx_amount'],
|
||||
label: filteredParams['label'] ?? filteredParams['recipient_name'],
|
||||
message: filteredParams['message'] ?? filteredParams['tx_description'],
|
||||
paymentId: filteredParams['tx_payment_id'], // Specific to Monero
|
||||
paymentId: filteredParams['tx_payment_id'],
|
||||
// Specific to Monero
|
||||
additionalParams: filteredParams,
|
||||
);
|
||||
} catch (e, s) {
|
||||
logging?.log("$e\n$s", level: LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a uri string with the given address and query parameters (if any)
|
||||
|
@ -304,7 +163,7 @@ class AddressUtils {
|
|||
Map<String, String> params,
|
||||
) {
|
||||
// Filter unrecognized parameters.
|
||||
final filteredParams = filterParams(params);
|
||||
final filteredParams = _filterParams(params);
|
||||
String uriString = "$scheme:$address";
|
||||
|
||||
if (scheme.toLowerCase() == "monero") {
|
||||
|
@ -345,11 +204,12 @@ class AddressUtils {
|
|||
}
|
||||
|
||||
/// Method to get CryptoCurrency based on URI scheme.
|
||||
static CryptoCurrency _getCryptoCurrencyByScheme(String scheme) {
|
||||
static CryptoCurrency? _getCryptoCurrencyByScheme(String scheme) {
|
||||
if (AppConfig.coins.map((e) => e.uriScheme).toSet().contains(scheme)) {
|
||||
return AppConfig.coins.firstWhere((e) => e.uriScheme == scheme);
|
||||
} else {
|
||||
throw UnsupportedError('Unsupported URI scheme: $scheme');
|
||||
return null;
|
||||
// throw UnsupportedError('Unsupported URI scheme: $scheme');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,21 +235,37 @@ class AddressUtils {
|
|||
}
|
||||
|
||||
class PaymentUriData {
|
||||
final CryptoCurrency coin;
|
||||
final String address;
|
||||
final String? scheme;
|
||||
final String? amount;
|
||||
final String? label;
|
||||
final String? message;
|
||||
final String? paymentId; // Specific to Monero.
|
||||
final Map<String, String> additionalParams;
|
||||
|
||||
CryptoCurrency? get coin => AddressUtils._getCryptoCurrencyByScheme(
|
||||
scheme ?? "", // empty will just return null
|
||||
);
|
||||
|
||||
PaymentUriData({
|
||||
required this.coin,
|
||||
required this.address,
|
||||
this.scheme,
|
||||
this.amount,
|
||||
this.label,
|
||||
this.message,
|
||||
this.paymentId,
|
||||
required this.additionalParams,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() => "PaymentUriData { "
|
||||
"coin: $coin, "
|
||||
"address: $address, "
|
||||
"amount: $amount, "
|
||||
"scheme: $scheme, "
|
||||
"label: $label, "
|
||||
"message: $message, "
|
||||
"paymentId: $paymentId, "
|
||||
"additionalParams: $additionalParams"
|
||||
" }";
|
||||
}
|
||||
|
|
|
@ -1809,18 +1809,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: fde692580bee3379374af1f624eb3e113ab2865ecb161dbe2d8ac2de9735dbdb
|
||||
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.5"
|
||||
version: "2.4.3"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "7ae52b23366e5295005022e62fa093f64bfe190810223ea0ebf733a4cd140bce"
|
||||
sha256: "1e62698dc1ab396152ccaf3b3990d826244e9f3c8c39b51805f209adcd6dbea3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.26"
|
||||
version: "0.5.22"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -12,52 +12,49 @@ void main() {
|
|||
|
||||
test("parse a valid uri string A", () {
|
||||
const uri = "dogecoin:$firoAddress?amount=50&label=eggs";
|
||||
final result = AddressUtils.parseUri(uri);
|
||||
expect(result, {
|
||||
"scheme": "dogecoin",
|
||||
"address": firoAddress,
|
||||
"amount": "50",
|
||||
"label": "eggs",
|
||||
});
|
||||
final result = AddressUtils.parsePaymentUri(uri);
|
||||
expect(result, isNotNull);
|
||||
expect(result!.scheme, "dogecoin");
|
||||
expect(result.address, firoAddress);
|
||||
expect(result.amount, "50");
|
||||
expect(result.label, "eggs");
|
||||
});
|
||||
|
||||
test("parse a valid uri string B", () {
|
||||
const uri = "firo:$firoAddress?amount=50&message=eggs+are+good";
|
||||
final result = AddressUtils.parseUri(uri);
|
||||
expect(result, {
|
||||
"scheme": "firo",
|
||||
"address": firoAddress,
|
||||
"amount": "50",
|
||||
"message": "eggs are good",
|
||||
});
|
||||
final result = AddressUtils.parsePaymentUri(uri);
|
||||
expect(result, isNotNull);
|
||||
expect(result!.scheme, "firo");
|
||||
expect(result.address, firoAddress);
|
||||
expect(result.amount, "50");
|
||||
expect(result.message, "eggs are good");
|
||||
});
|
||||
|
||||
test("parse a valid uri string C", () {
|
||||
const uri = "bitcoin:$firoAddress?amount=50.1&message=eggs%20are%20good%21";
|
||||
final result = AddressUtils.parseUri(uri);
|
||||
expect(result, {
|
||||
"scheme": "bitcoin",
|
||||
"address": firoAddress,
|
||||
"amount": "50.1",
|
||||
"message": "eggs are good!",
|
||||
});
|
||||
final result = AddressUtils.parsePaymentUri(uri);
|
||||
expect(result, isNotNull);
|
||||
expect(result!.scheme, "bitcoin");
|
||||
expect(result.address, firoAddress);
|
||||
expect(result.amount, "50.1");
|
||||
expect(result.message, "eggs are good!");
|
||||
});
|
||||
|
||||
test("parse an invalid uri string", () {
|
||||
const uri = "firo$firoAddress?amount=50&label=eggs";
|
||||
final result = AddressUtils.parseUri(uri);
|
||||
expect(result, {});
|
||||
final result = AddressUtils.parsePaymentUri(uri);
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test("parse an invalid string", () {
|
||||
const uri = "$firoAddress?amount=50&label=eggs";
|
||||
final result = AddressUtils.parseUri(uri);
|
||||
expect(result, {});
|
||||
final result = AddressUtils.parsePaymentUri(uri);
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test("parse an invalid uri string", () {
|
||||
const uri = "::: 8 \\ %23";
|
||||
expect(AddressUtils.parseUri(uri), {});
|
||||
expect(AddressUtils.parsePaymentUri(uri), isNull);
|
||||
});
|
||||
|
||||
test("encode a list of (mnemonic) words/strings as a json object", () {
|
||||
|
|
Loading…
Reference in a new issue