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 = [];
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
addressLabelController = TextEditingController()
|
addressLabelController = TextEditingController()
|
||||||
|
@ -404,71 +460,7 @@ class _NewContactAddressEntryFormState
|
||||||
null)
|
null)
|
||||||
TextFieldIconButton(
|
TextFieldIconButton(
|
||||||
key: const Key("addAddressBookEntryScanQrButtonKey"),
|
key: const Key("addAddressBookEntryScanQrButtonKey"),
|
||||||
onTap: () async {
|
onTap: _onQrTapped,
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
child: const QrCodeIcon(),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_receiveAddressController = TextEditingController();
|
_receiveAddressController = TextEditingController();
|
||||||
|
@ -1375,63 +1429,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
||||||
!isDesktop)
|
!isDesktop)
|
||||||
TextFieldIconButton(
|
TextFieldIconButton(
|
||||||
key: const Key("buyViewScanQrButtonKey"),
|
key: const Key("buyViewScanQrButtonKey"),
|
||||||
onTap: () async {
|
onTap: _onQrTapped,
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
child: const QrCodeIcon(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -70,6 +70,78 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
||||||
|
|
||||||
bool enableNext = false;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
model = widget.model;
|
model = widget.model;
|
||||||
|
@ -137,7 +209,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -405,50 +477,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
||||||
key: const Key(
|
key: const Key(
|
||||||
"sendViewScanQrButtonKey",
|
"sendViewScanQrButtonKey",
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: _onToQrTapped,
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
child: const QrCodeIcon(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -685,51 +714,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
||||||
key: const Key(
|
key: const Key(
|
||||||
"sendViewScanQrButtonKey",
|
"sendViewScanQrButtonKey",
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: _onRefundQrTapped,
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
child: const QrCodeIcon(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -97,7 +97,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
initialDirectory: dir.path,
|
initialDirectory: dir.path,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (path != null) {
|
if (path != null && mounted) {
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
if (file.existsSync()) {
|
if (file.existsSync()) {
|
||||||
unawaited(
|
unawaited(
|
||||||
|
@ -109,13 +109,15 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await file.writeAsBytes(pngBytes);
|
await file.writeAsBytes(pngBytes);
|
||||||
unawaited(
|
if (mounted) {
|
||||||
showFloatingFlushBar(
|
unawaited(
|
||||||
type: FlushBarType.success,
|
showFloatingFlushBar(
|
||||||
message: "$path saved!",
|
type: FlushBarType.success,
|
||||||
context: context,
|
message: "$path saved!",
|
||||||
),
|
context: context,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,7 +169,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
final Map<String, String> queryParams = {};
|
final Map<String, String> queryParams = {};
|
||||||
|
|
||||||
if (amountString.isNotEmpty) {
|
if (amountString.isNotEmpty) {
|
||||||
queryParams["amount"] = amountString;
|
queryParams["amount"] = amount.toString();
|
||||||
}
|
}
|
||||||
if (noteString.isNotEmpty) {
|
if (noteString.isNotEmpty) {
|
||||||
queryParams["message"] = noteString;
|
queryParams["message"] = noteString;
|
||||||
|
@ -311,7 +313,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 70));
|
await Future<void>.delayed(const Duration(milliseconds: 70));
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context).pop();
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
addressController = TextEditingController();
|
addressController = TextEditingController();
|
||||||
|
@ -289,76 +352,7 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
key: const Key(
|
key: const Key(
|
||||||
"sendViewScanQrButtonKey",
|
"sendViewScanQrButtonKey",
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: _onQrTapped,
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
child: const QrCodeIcon(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -163,9 +163,13 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
level: LogLevel.Info,
|
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
|
// auto fill address
|
||||||
_address = paymentData.address.trim();
|
_address = paymentData.address.trim();
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
@ -195,12 +199,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// now check for non standard encoded basic address
|
// now check for non standard encoded basic address
|
||||||
} else if (ref
|
} else {
|
||||||
.read(pWallets)
|
_address = qrResult.rawContent.split("\n").first.trim();
|
||||||
.getWallet(walletId)
|
|
||||||
.cryptoCurrency
|
|
||||||
.validateAddress(qrResult.rawContent)) {
|
|
||||||
_address = qrResult.rawContent.trim();
|
|
||||||
sendToController.text = _address ?? "";
|
sendToController.text = _address ?? "";
|
||||||
|
|
||||||
_setValidAddressProviders(_address);
|
_setValidAddressProviders(_address);
|
||||||
|
|
|
@ -163,25 +163,30 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
||||||
level: LogLevel.Info,
|
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
|
// auto fill address
|
||||||
_address = (results["address"] ?? "").trim();
|
_address = paymentData.address.trim();
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
|
||||||
// autofill notes field
|
// autofill notes field
|
||||||
if (results["message"] != null) {
|
if (paymentData.message != null) {
|
||||||
noteController.text = results["message"]!;
|
noteController.text = paymentData.message!;
|
||||||
} else if (results["label"] != null) {
|
} else if (paymentData.label != null) {
|
||||||
noteController.text = results["label"]!;
|
noteController.text = paymentData.label!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// autofill amount field
|
// autofill amount field
|
||||||
if (results["amount"] != null) {
|
if (paymentData.amount != null) {
|
||||||
final Amount amount = Decimal.parse(results["amount"]!).toAmount(
|
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||||
fractionDigits: tokenContract.decimals,
|
fractionDigits: tokenContract.decimals,
|
||||||
);
|
);
|
||||||
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
||||||
|
@ -198,12 +203,8 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// now check for non standard encoded basic address
|
// now check for non standard encoded basic address
|
||||||
} else if (ref
|
} else {
|
||||||
.read(pWallets)
|
_address = qrResult.rawContent.split("\n").first.trim();
|
||||||
.getWallet(walletId)
|
|
||||||
.cryptoCurrency
|
|
||||||
.validateAddress(qrResult.rawContent)) {
|
|
||||||
_address = qrResult.rawContent.trim();
|
|
||||||
sendToController.text = _address ?? "";
|
sendToController.text = _address ?? "";
|
||||||
|
|
||||||
_updatePreviewButtonState(_address, _amountToSend);
|
_updatePreviewButtonState(_address, _amountToSend);
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
|
|
||||||
Future<void> scanWebcam() async {
|
Future<void> scanWebcam() async {
|
||||||
try {
|
try {
|
||||||
await showDialog(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return QrCodeScannerDialog(
|
return QrCodeScannerDialog(
|
||||||
|
@ -153,16 +153,20 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
try {
|
try {
|
||||||
_processQrCodeData(qrCodeData);
|
_processQrCodeData(qrCodeData);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Error processing QR code data: $e\n$s",
|
Logging.instance.log(
|
||||||
level: LogLevel.Error);
|
"Error processing QR code data: $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Error opening QR code scanner dialog: $e\n$s",
|
Logging.instance.log(
|
||||||
level: LogLevel.Error);
|
"Error opening QR code scanner dialog: $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,84 +659,15 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
// return null;
|
// 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) {
|
void _processQrCodeData(String qrCodeData) {
|
||||||
try {
|
try {
|
||||||
final paymentData = AddressUtils.parsePaymentUri(qrCodeData);
|
final paymentData = AddressUtils.parsePaymentUri(
|
||||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
qrCodeData,
|
||||||
|
logging: Logging.instance,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (paymentData != null &&
|
||||||
|
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||||
// Auto fill address.
|
// Auto fill address.
|
||||||
_address = paymentData.address.trim();
|
_address = paymentData.address.trim();
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
@ -756,6 +691,14 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
_note = paymentData.label;
|
_note = paymentData.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setValidAddressProviders(_address);
|
||||||
|
setState(() {
|
||||||
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_address = qrCodeData.split("\n").first.trim();
|
||||||
|
sendToController.text = _address ?? "";
|
||||||
|
|
||||||
_setValidAddressProviders(_address);
|
_setValidAddressProviders(_address);
|
||||||
setState(() {
|
setState(() {
|
||||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
|
@ -807,8 +750,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final paymentData = AddressUtils.parsePaymentUri(content);
|
final paymentData = AddressUtils.parsePaymentUri(
|
||||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
content,
|
||||||
|
logging: Logging.instance,
|
||||||
|
);
|
||||||
|
if (paymentData != null &&
|
||||||
|
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||||
// auto fill address
|
// auto fill address
|
||||||
_address = paymentData.address;
|
_address = paymentData.address;
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
@ -837,6 +784,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
content = content.split("\n").first.trim();
|
||||||
if (coin is Epiccash) {
|
if (coin is Epiccash) {
|
||||||
content = AddressUtils().formatAddress(content);
|
content = AddressUtils().formatAddress(content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,12 @@ import 'package:decimal/decimal.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import '../../../../models/isar/models/contact_entry.dart';
|
import '../../../../models/isar/models/contact_entry.dart';
|
||||||
import '../../../../models/paynym/paynym_account_lite.dart';
|
import '../../../../models/paynym/paynym_account_lite.dart';
|
||||||
import '../../../../models/send_view_auto_fill_data.dart';
|
import '../../../../models/send_view_auto_fill_data.dart';
|
||||||
import '../../../../pages/send_view/confirm_transaction_view.dart';
|
import '../../../../pages/send_view/confirm_transaction_view.dart';
|
||||||
import '../../../../pages/send_view/sub_widgets/building_transaction_dialog.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/providers.dart';
|
||||||
import '../../../../providers/ui/fee_rate_type_state_provider.dart';
|
import '../../../../providers/ui/fee_rate_type_state_provider.dart';
|
||||||
import '../../../../providers/ui/preview_tx_button_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/icon_widgets/x_icon.dart';
|
||||||
import '../../../../widgets/stack_text_field.dart';
|
import '../../../../widgets/stack_text_field.dart';
|
||||||
import '../../../../widgets/textfield_icon_button.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})$';
|
// 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,
|
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
|
// auto fill address
|
||||||
_address = results["address"] ?? "";
|
_address = paymentData.address.trim();
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
|
||||||
// autofill notes field
|
// autofill notes field
|
||||||
if (results["message"] != null) {
|
if (paymentData.message != null) {
|
||||||
_note = results["message"]!;
|
_note = paymentData.message!;
|
||||||
} else if (results["label"] != null) {
|
} else if (paymentData.label != null) {
|
||||||
_note = results["label"]!;
|
_note = paymentData.label!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// autofill amount field
|
// autofill amount field
|
||||||
if (results["amount"] != null) {
|
if (paymentData.amount != null) {
|
||||||
final amount = Decimal.parse(results["amount"]!).toAmount(
|
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||||
fractionDigits:
|
fractionDigits:
|
||||||
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
|
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
|
||||||
);
|
);
|
||||||
|
@ -516,12 +522,8 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// now check for non standard encoded basic address
|
// now check for non standard encoded basic address
|
||||||
} else if (ref
|
} else {
|
||||||
.read(pWallets)
|
_address = qrResult.rawContent.split("\n").first.trim();
|
||||||
.getWallet(walletId)
|
|
||||||
.cryptoCurrency
|
|
||||||
.validateAddress(qrResult.rawContent)) {
|
|
||||||
_address = qrResult.rawContent;
|
|
||||||
sendToController.text = _address ?? "";
|
sendToController.text = _address ?? "";
|
||||||
|
|
||||||
_updatePreviewButtonState(_address, _amountToSend);
|
_updatePreviewButtonState(_address, _amountToSend);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'dart:convert';
|
||||||
|
|
||||||
import '../app_config.dart';
|
import '../app_config.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
import 'logger.dart';
|
||||||
|
|
||||||
class AddressUtils {
|
class AddressUtils {
|
||||||
static final Set<String> recognizedParams = {
|
static final Set<String> recognizedParams = {
|
||||||
|
@ -29,166 +30,16 @@ class AddressUtils {
|
||||||
return '${address.substring(0, 5)}...${address.substring(address.length - 5)}';
|
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.
|
/// Return only recognized parameters.
|
||||||
static Map<String, String> filterParams(Map<String, String> params) {
|
static Map<String, String> _filterParams(Map<String, String> params) {
|
||||||
return Map.fromEntries(params.entries
|
return Map.fromEntries(
|
||||||
.where((entry) => recognizedParams.contains(entry.key.toLowerCase())));
|
params.entries
|
||||||
|
.where((entry) => recognizedParams.contains(entry.key.toLowerCase())),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a URI string and returns a map with parsed components.
|
/// 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 = {};
|
final Map<String, String> result = {};
|
||||||
try {
|
try {
|
||||||
final u = Uri.parse(uri);
|
final u = Uri.parse(uri);
|
||||||
|
@ -273,28 +124,36 @@ class AddressUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Centralized method to handle various cryptocurrency URIs and return a common object.
|
/// 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.
|
// Normalize the URI scheme.
|
||||||
final String scheme = parsedData['scheme'] ?? '';
|
final String scheme = parsedData['scheme'] ?? '';
|
||||||
parsedData.remove('scheme');
|
parsedData.remove('scheme');
|
||||||
|
|
||||||
// Determine the coin type based on the URI scheme.
|
// Filter out unrecognized parameters.
|
||||||
final CryptoCurrency coin = _getCryptoCurrencyByScheme(scheme);
|
final filteredParams = _filterParams(parsedData);
|
||||||
|
|
||||||
// Filter out unrecognized parameters.
|
return PaymentUriData(
|
||||||
final filteredParams = filterParams(parsedData);
|
scheme: scheme,
|
||||||
|
address: parsedData['address']!.trim(),
|
||||||
return PaymentUriData(
|
amount: filteredParams['amount'] ?? filteredParams['tx_amount'],
|
||||||
coin: coin,
|
label: filteredParams['label'] ?? filteredParams['recipient_name'],
|
||||||
address: parsedData['address'] ?? '',
|
message: filteredParams['message'] ?? filteredParams['tx_description'],
|
||||||
amount: filteredParams['amount'] ?? filteredParams['tx_amount'],
|
paymentId: filteredParams['tx_payment_id'],
|
||||||
label: filteredParams['label'] ?? filteredParams['recipient_name'],
|
// Specific to Monero
|
||||||
message: filteredParams['message'] ?? filteredParams['tx_description'],
|
additionalParams: filteredParams,
|
||||||
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)
|
/// Builds a uri string with the given address and query parameters (if any)
|
||||||
|
@ -304,7 +163,7 @@ class AddressUtils {
|
||||||
Map<String, String> params,
|
Map<String, String> params,
|
||||||
) {
|
) {
|
||||||
// Filter unrecognized parameters.
|
// Filter unrecognized parameters.
|
||||||
final filteredParams = filterParams(params);
|
final filteredParams = _filterParams(params);
|
||||||
String uriString = "$scheme:$address";
|
String uriString = "$scheme:$address";
|
||||||
|
|
||||||
if (scheme.toLowerCase() == "monero") {
|
if (scheme.toLowerCase() == "monero") {
|
||||||
|
@ -345,11 +204,12 @@ class AddressUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Method to get CryptoCurrency based on URI scheme.
|
/// 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)) {
|
if (AppConfig.coins.map((e) => e.uriScheme).toSet().contains(scheme)) {
|
||||||
return AppConfig.coins.firstWhere((e) => e.uriScheme == scheme);
|
return AppConfig.coins.firstWhere((e) => e.uriScheme == scheme);
|
||||||
} else {
|
} else {
|
||||||
throw UnsupportedError('Unsupported URI scheme: $scheme');
|
return null;
|
||||||
|
// throw UnsupportedError('Unsupported URI scheme: $scheme');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,21 +235,37 @@ class AddressUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaymentUriData {
|
class PaymentUriData {
|
||||||
final CryptoCurrency coin;
|
|
||||||
final String address;
|
final String address;
|
||||||
|
final String? scheme;
|
||||||
final String? amount;
|
final String? amount;
|
||||||
final String? label;
|
final String? label;
|
||||||
final String? message;
|
final String? message;
|
||||||
final String? paymentId; // Specific to Monero.
|
final String? paymentId; // Specific to Monero.
|
||||||
final Map<String, String> additionalParams;
|
final Map<String, String> additionalParams;
|
||||||
|
|
||||||
|
CryptoCurrency? get coin => AddressUtils._getCryptoCurrencyByScheme(
|
||||||
|
scheme ?? "", // empty will just return null
|
||||||
|
);
|
||||||
|
|
||||||
PaymentUriData({
|
PaymentUriData({
|
||||||
required this.coin,
|
|
||||||
required this.address,
|
required this.address,
|
||||||
|
this.scheme,
|
||||||
this.amount,
|
this.amount,
|
||||||
this.label,
|
this.label,
|
||||||
this.message,
|
this.message,
|
||||||
this.paymentId,
|
this.paymentId,
|
||||||
required this.additionalParams,
|
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"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: fde692580bee3379374af1f624eb3e113ab2865ecb161dbe2d8ac2de9735dbdb
|
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5"
|
version: "2.4.3"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: "7ae52b23366e5295005022e62fa093f64bfe190810223ea0ebf733a4cd140bce"
|
sha256: "1e62698dc1ab396152ccaf3b3990d826244e9f3c8c39b51805f209adcd6dbea3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.26"
|
version: "0.5.22"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -12,52 +12,49 @@ void main() {
|
||||||
|
|
||||||
test("parse a valid uri string A", () {
|
test("parse a valid uri string A", () {
|
||||||
const uri = "dogecoin:$firoAddress?amount=50&label=eggs";
|
const uri = "dogecoin:$firoAddress?amount=50&label=eggs";
|
||||||
final result = AddressUtils.parseUri(uri);
|
final result = AddressUtils.parsePaymentUri(uri);
|
||||||
expect(result, {
|
expect(result, isNotNull);
|
||||||
"scheme": "dogecoin",
|
expect(result!.scheme, "dogecoin");
|
||||||
"address": firoAddress,
|
expect(result.address, firoAddress);
|
||||||
"amount": "50",
|
expect(result.amount, "50");
|
||||||
"label": "eggs",
|
expect(result.label, "eggs");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parse a valid uri string B", () {
|
test("parse a valid uri string B", () {
|
||||||
const uri = "firo:$firoAddress?amount=50&message=eggs+are+good";
|
const uri = "firo:$firoAddress?amount=50&message=eggs+are+good";
|
||||||
final result = AddressUtils.parseUri(uri);
|
final result = AddressUtils.parsePaymentUri(uri);
|
||||||
expect(result, {
|
expect(result, isNotNull);
|
||||||
"scheme": "firo",
|
expect(result!.scheme, "firo");
|
||||||
"address": firoAddress,
|
expect(result.address, firoAddress);
|
||||||
"amount": "50",
|
expect(result.amount, "50");
|
||||||
"message": "eggs are good",
|
expect(result.message, "eggs are good");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parse a valid uri string C", () {
|
test("parse a valid uri string C", () {
|
||||||
const uri = "bitcoin:$firoAddress?amount=50.1&message=eggs%20are%20good%21";
|
const uri = "bitcoin:$firoAddress?amount=50.1&message=eggs%20are%20good%21";
|
||||||
final result = AddressUtils.parseUri(uri);
|
final result = AddressUtils.parsePaymentUri(uri);
|
||||||
expect(result, {
|
expect(result, isNotNull);
|
||||||
"scheme": "bitcoin",
|
expect(result!.scheme, "bitcoin");
|
||||||
"address": firoAddress,
|
expect(result.address, firoAddress);
|
||||||
"amount": "50.1",
|
expect(result.amount, "50.1");
|
||||||
"message": "eggs are good!",
|
expect(result.message, "eggs are good!");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parse an invalid uri string", () {
|
test("parse an invalid uri string", () {
|
||||||
const uri = "firo$firoAddress?amount=50&label=eggs";
|
const uri = "firo$firoAddress?amount=50&label=eggs";
|
||||||
final result = AddressUtils.parseUri(uri);
|
final result = AddressUtils.parsePaymentUri(uri);
|
||||||
expect(result, {});
|
expect(result, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parse an invalid string", () {
|
test("parse an invalid string", () {
|
||||||
const uri = "$firoAddress?amount=50&label=eggs";
|
const uri = "$firoAddress?amount=50&label=eggs";
|
||||||
final result = AddressUtils.parseUri(uri);
|
final result = AddressUtils.parsePaymentUri(uri);
|
||||||
expect(result, {});
|
expect(result, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parse an invalid uri string", () {
|
test("parse an invalid uri string", () {
|
||||||
const uri = "::: 8 \\ %23";
|
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", () {
|
test("encode a list of (mnemonic) words/strings as a json object", () {
|
||||||
|
|
Loading…
Reference in a new issue