handle refund address support

This commit is contained in:
julian 2023-02-06 13:45:22 -06:00
parent b19a3dbbf8
commit 8061f0811d
2 changed files with 285 additions and 250 deletions

View file

@ -8,6 +8,7 @@ import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart';
@ -123,6 +124,10 @@ class _Step2ViewState extends ConsumerState<Step2View> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final supportsRefund =
ref.watch(currentExchangeNameStateProvider.state).state !=
MajesticBankExchange.exchangeName;
return Background( return Background(
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background, backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -217,8 +222,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() { setState(() {
enableNext = enableNext =
_toController.text.isNotEmpty && _toController.text.isNotEmpty &&
_refundController (_refundController
.text.isNotEmpty; .text.isNotEmpty ||
!supportsRefund);
}); });
} }
}); });
@ -291,8 +297,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() { setState(() {
enableNext = _toController enableNext = _toController
.text.isNotEmpty && .text.isNotEmpty &&
_refundController (_refundController.text
.text.isNotEmpty; .isNotEmpty ||
!supportsRefund);
}); });
}, },
child: const XIcon(), child: const XIcon(),
@ -318,8 +325,10 @@ class _Step2ViewState extends ConsumerState<Step2View> {
enableNext = _toController enableNext = _toController
.text .text
.isNotEmpty && .isNotEmpty &&
_refundController (_refundController
.text.isNotEmpty; .text
.isNotEmpty ||
!supportsRefund);
}); });
} }
}, },
@ -367,8 +376,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() { setState(() {
enableNext = _toController enableNext = _toController
.text.isNotEmpty && .text.isNotEmpty &&
_refundController (_refundController.text
.text.isNotEmpty; .isNotEmpty ||
!supportsRefund);
}); });
}); });
}, },
@ -396,8 +406,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() { setState(() {
enableNext = _toController enableNext = _toController
.text.isNotEmpty && .text.isNotEmpty &&
_refundController (_refundController.text
.text.isNotEmpty; .isNotEmpty ||
!supportsRefund);
}); });
} else { } else {
_toController.text = _toController.text =
@ -408,8 +419,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() { setState(() {
enableNext = _toController enableNext = _toController
.text.isNotEmpty && .text.isNotEmpty &&
_refundController (_refundController.text
.text.isNotEmpty; .isNotEmpty ||
!supportsRefund);
}); });
} }
} on PlatformException catch (e, s) { } on PlatformException catch (e, s) {
@ -440,133 +452,230 @@ class _Step2ViewState extends ConsumerState<Step2View> {
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
Row( if (supportsRefund)
mainAxisAlignment: MainAxisAlignment.spaceBetween, Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
Text( children: [
"Refund Wallet (required)", Text(
style: STextStyles.smallMed12(context), "Refund Wallet (required)",
), style: STextStyles.smallMed12(context),
if (isStackCoin(model.sendTicker)) ),
CustomTextButton( if (isStackCoin(model.sendTicker))
text: "Choose from Stack", CustomTextButton(
onTap: () { text: "Choose from Stack",
try { onTap: () {
final coin = try {
coinFromTickerCaseInsensitive( final coin =
model.sendTicker, coinFromTickerCaseInsensitive(
); model.sendTicker,
Navigator.of(context) );
.pushNamed( Navigator.of(context)
ChooseFromStackView.routeName, .pushNamed(
arguments: coin, ChooseFromStackView.routeName,
) arguments: coin,
.then((value) async { )
if (value is String) { .then((value) async {
final manager = ref if (value is String) {
.read( final manager = ref
walletsChangeNotifierProvider) .read(
.getManager(value); walletsChangeNotifierProvider)
.getManager(value);
_refundController.text = _refundController.text =
manager.walletName; manager.walletName;
model.refundAddress = await manager model.refundAddress = await manager
.currentReceivingAddress; .currentReceivingAddress;
} }
setState(() { setState(() {
enableNext = _toController enableNext =
.text.isNotEmpty && _toController.text.isNotEmpty &&
_refundController.text.isNotEmpty; _refundController
.text.isNotEmpty;
});
}); });
}); } catch (e, s) {
} catch (e, s) { Logging.instance.log("$e\n$s",
Logging.instance level: LogLevel.Info);
.log("$e\n$s", level: LogLevel.Info); }
} },
}, ),
), ],
],
),
const SizedBox(
height: 4,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
), ),
child: TextField( if (supportsRefund)
key: const Key( const SizedBox(
"refundExchangeStep2ViewAddressFieldKey"), height: 4,
controller: _refundController, ),
readOnly: false, if (supportsRefund)
autocorrect: false, ClipRRect(
enableSuggestions: false, borderRadius: BorderRadius.circular(
// inputFormatters: <TextInputFormatter>[ Constants.size.circularBorderRadius,
// FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")),
// ],
toolbarOptions: const ToolbarOptions(
copy: false,
cut: false,
paste: true,
selectAll: false,
), ),
focusNode: _refundFocusNode, child: TextField(
style: STextStyles.field(context), key: const Key(
onChanged: (value) { "refundExchangeStep2ViewAddressFieldKey"),
setState(() {}); controller: _refundController,
}, readOnly: false,
decoration: standardInputDecoration( autocorrect: false,
"Enter ${model.sendTicker.toUpperCase()} refund address", enableSuggestions: false,
_refundFocusNode, // inputFormatters: <TextInputFormatter>[
context, // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")),
).copyWith( // ],
contentPadding: const EdgeInsets.only( toolbarOptions: const ToolbarOptions(
left: 16, copy: false,
top: 6, cut: false,
bottom: 8, paste: true,
right: 5, selectAll: false,
), ),
suffixIcon: Padding( focusNode: _refundFocusNode,
padding: _refundController.text.isEmpty style: STextStyles.field(context),
? const EdgeInsets.only(right: 16) onChanged: (value) {
: const EdgeInsets.only(right: 0), setState(() {});
child: UnconstrainedBox( },
child: Row( decoration: standardInputDecoration(
mainAxisAlignment: "Enter ${model.sendTicker.toUpperCase()} refund address",
MainAxisAlignment.spaceAround, _refundFocusNode,
children: [ context,
_refundController.text.isNotEmpty ).copyWith(
? TextFieldIconButton( contentPadding: const EdgeInsets.only(
key: const Key( left: 16,
"sendViewClearAddressFieldButtonKey"), top: 6,
onTap: () { bottom: 8,
_refundController.text = ""; right: 5,
model.refundAddress = ),
_refundController.text; suffixIcon: Padding(
padding: _refundController.text.isEmpty
? const EdgeInsets.only(right: 16)
: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
_refundController.text.isNotEmpty
? TextFieldIconButton(
key: const Key(
"sendViewClearAddressFieldButtonKey"),
onTap: () {
_refundController.text = "";
model.refundAddress =
_refundController.text;
setState(() {
enableNext = _toController
.text
.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
},
child: const XIcon(),
)
: TextFieldIconButton(
key: const Key(
"sendViewPasteAddressFieldButtonKey"),
onTap: () async {
final ClipboardData? data =
await clipboard.getData(
Clipboard
.kTextPlain);
if (data?.text != null &&
data!
.text!.isNotEmpty) {
final content =
data.text!.trim();
_refundController.text =
content;
model.refundAddress =
_refundController
.text;
setState(() {
enableNext = _toController
.text
.isNotEmpty &&
_refundController
.text
.isNotEmpty;
});
}
},
child: _refundController
.text.isEmpty
? const ClipboardIcon()
: const XIcon(),
),
if (_refundController.text.isEmpty)
TextFieldIconButton(
key: const Key(
"sendViewAddressBookButtonKey"),
onTap: () {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = true;
Navigator.of(context)
.pushNamed(
AddressBookView.routeName,
)
.then((_) {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = false;
final address = ref
.read(
exchangeFromAddressBookAddressStateProvider
.state)
.state;
if (address.isNotEmpty) {
_refundController.text =
address;
model.refundAddress =
_refundController.text;
}
setState(() { setState(() {
enableNext = _toController enableNext = _toController
.text.isNotEmpty && .text.isNotEmpty &&
_refundController _refundController
.text.isNotEmpty; .text.isNotEmpty;
}); });
}, });
child: const XIcon(), },
) child: const AddressBookIcon(),
: TextFieldIconButton( ),
key: const Key( if (_refundController.text.isEmpty)
"sendViewPasteAddressFieldButtonKey"), TextFieldIconButton(
onTap: () async { key: const Key(
final ClipboardData? data = "sendViewScanQrButtonKey"),
await clipboard.getData( onTap: () async {
Clipboard.kTextPlain); try {
if (data?.text != null && final qrResult =
data!.text!.isNotEmpty) { await scanner.scan();
final content =
data.text!.trim();
final results =
AddressUtils.parseUri(
qrResult.rawContent);
if (results.isNotEmpty) {
// auto fill address
_refundController.text = _refundController.text =
content; results["address"] ??
"";
model.refundAddress =
_refundController.text;
setState(() {
enableNext = _toController
.text
.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
} else {
_refundController.text =
qrResult.rawContent;
model.refundAddress = model.refundAddress =
_refundController.text; _refundController.text;
@ -578,116 +687,33 @@ class _Step2ViewState extends ConsumerState<Step2View> {
.text.isNotEmpty; .text.isNotEmpty;
}); });
} }
}, } on PlatformException catch (e, s) {
child: _refundController Logging.instance.log(
.text.isEmpty "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
? const ClipboardIcon() level: LogLevel.Warning,
: const XIcon(), );
),
if (_refundController.text.isEmpty)
TextFieldIconButton(
key: const Key(
"sendViewAddressBookButtonKey"),
onTap: () {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = true;
Navigator.of(context)
.pushNamed(
AddressBookView.routeName,
)
.then((_) {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = false;
final address = ref
.read(
exchangeFromAddressBookAddressStateProvider
.state)
.state;
if (address.isNotEmpty) {
_refundController.text =
address;
model.refundAddress =
_refundController.text;
} }
setState(() { },
enableNext = _toController child: const QrCodeIcon(),
.text.isNotEmpty && ),
_refundController ],
.text.isNotEmpty; ),
});
});
},
child: const AddressBookIcon(),
),
if (_refundController.text.isEmpty)
TextFieldIconButton(
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,
);
}
},
child: const QrCodeIcon(),
),
],
), ),
), ),
), ),
), ),
), ),
), if (supportsRefund)
const SizedBox( const SizedBox(
height: 6, height: 6,
), ),
RoundedWhiteContainer( if (supportsRefund)
child: Text( RoundedWhiteContainer(
"In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.", child: Text(
style: STextStyles.label(context), "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.",
style: STextStyles.label(context),
),
), ),
),
const Spacer(), const Spacer(),
Row( Row(
children: [ children: [

View file

@ -7,9 +7,11 @@ import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart';
import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart';
import 'package:stackwallet/providers/exchange/exchange_provider.dart'; import 'package:stackwallet/providers/exchange/exchange_provider.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart';
@ -51,6 +53,10 @@ class _Step3ViewState extends ConsumerState<Step3View> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final supportsRefund =
ref.watch(currentExchangeNameStateProvider.state).state !=
MajesticBankExchange.exchangeName;
return Background( return Background(
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background, backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -174,27 +180,29 @@ class _Step3ViewState extends ConsumerState<Step3View> {
], ],
), ),
), ),
const SizedBox( if (supportsRefund)
height: 8, const SizedBox(
), height: 8,
RoundedWhiteContainer( ),
child: Column( if (supportsRefund)
crossAxisAlignment: CrossAxisAlignment.start, RoundedWhiteContainer(
children: [ child: Column(
Text( crossAxisAlignment: CrossAxisAlignment.start,
"Refund ${model.sendTicker.toUpperCase()} address", children: [
style: STextStyles.itemSubtitle(context), Text(
), "Refund ${model.sendTicker.toUpperCase()} address",
const SizedBox( style: STextStyles.itemSubtitle(context),
height: 4, ),
), const SizedBox(
Text( height: 4,
model.refundAddress!, ),
style: STextStyles.itemSubtitle12(context), Text(
) model.refundAddress!,
], style: STextStyles.itemSubtitle12(context),
)
],
),
), ),
),
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
@ -259,8 +267,9 @@ class _Step3ViewState extends ConsumerState<Step3View> {
addressTo: addressTo:
model.recipientAddress!, model.recipientAddress!,
extraId: null, extraId: null,
addressRefund: addressRefund: supportsRefund
model.refundAddress!, ? model.refundAddress!
: "",
refundExtraId: "", refundExtraId: "",
rateId: model.rateId, rateId: model.rateId,
reversed: model.reversed, reversed: model.reversed,