Skip to content

Commit

Permalink
dapp: Improve UI feedback during pending transactions.
Browse files Browse the repository at this point in the history
  • Loading branch information
patniemeyer committed Oct 2, 2024
1 parent 9fdb5a1 commit bd659d9
Show file tree
Hide file tree
Showing 18 changed files with 178 additions and 74 deletions.
2 changes: 0 additions & 2 deletions gui-orchid/lib/util/format_decimal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ String formatDecimal(

// Implement the min/max manually
restricted = trimAndPadDecimal(restricted, minPrecision, maxPrecision);
print("XXX: restricted: $restricted");

var precisionIndicator = '';
if (showPrecisionIndicator) {
Expand All @@ -122,7 +121,6 @@ String formatDecimal(
// Workaround: no internationalization at this stage
final unrestricted = value.toString();

print("XXX: unrestricted: $unrestricted");
precisionIndicator =
restricted.length < unrestricted.length ? ellipsis : '';
}
Expand Down
44 changes: 43 additions & 1 deletion web-ethereum/account_dapp/lib/dapp/orchid/dapp_button.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:orchid/orchid/orchid.dart';
import 'package:flutter/material.dart';
import 'package:orchid/gui-orchid/lib/orchid/orchid_circular_progress.dart';
import 'package:orchid/orchid/orchid_action_button.dart';

class DappButton extends StatelessWidget {
Expand All @@ -19,7 +21,8 @@ class DappButton extends StatelessWidget {

/// use double.infinity for an expandable button, null for the default width
this.width,
this.backgroundColor, this.height,
this.backgroundColor,
this.height,
}) : super(key: key);

@override
Expand All @@ -36,3 +39,42 @@ class DappButton extends StatelessWidget {
);
}
}

// Extend DappButton with a transaction status indicator.
class DappTransactionButton extends DappButton {
final bool txPending;

const DappTransactionButton({
Key? key,
required String text,
required VoidCallback? onPressed,
Widget? trailing,
TextStyle? textStyle,
double? width,
double? height,
Color? backgroundColor,
required this.txPending,
}) : super(
key: key,
text: text,
onPressed: onPressed,
trailing: trailing,
textStyle: textStyle,
width: width,
height: height,
backgroundColor: backgroundColor,
);

@override
Widget build(BuildContext context) {
return DappButton(
text: txPending ? "Waiting for Transaction" : text,
trailing: txPending ? OrchidCircularProgressIndicator.smallIndeterminate().left(16) : trailing,
onPressed: onPressed,
textStyle: textStyle,
width: width,
height: height,
backgroundColor: backgroundColor,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import 'dart:math';
import 'package:orchid/api/orchid_eth/chains.dart';
import 'package:orchid/dapp/preferences/dapp_transaction.dart';
import 'package:orchid/orchid/orchid.dart';
import 'package:orchid/dapp/orchid_web3/orchid_web3_context.dart';
import 'package:orchid/dapp/preferences/user_preferences_dapp.dart';
Expand Down
23 changes: 19 additions & 4 deletions web-ethereum/account_dapp/lib/dapp/orchid_web3/orchid_erc20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,26 @@ class OrchidERC20 {

// For a transaction that uses an ERC20 token, the transaction may require an approval
class ERC20PayableTransactionCallbacks {
final Future<void> Function(String approvalHash) onApproval;
final Future<void> Function(String txHash) onTransaction;
final Future<void> Function(String txHash, int seriesIndex, int seriesTotal) onApprovalCallback;
final Future<void> Function(String txHash, int seriesIndex, int seriesTotal) onTransactionCallback;

bool _hasApproval = false;

ERC20PayableTransactionCallbacks({
required this.onApproval,
required this.onTransaction,
required this.onApprovalCallback,
required this.onTransactionCallback,
});

Future<void> onApproval(String txHash) async {
_hasApproval = true;
return onApprovalCallback(txHash, 1, 2); // Approval is the first in the series (1 of 2)
}

Future<void> onTransaction(String txHash) async {
if (_hasApproval) {
return onTransactionCallback(txHash, 2, 2); // If approval has happened, this is the second in the series (2 of 2)
} else {
return onTransactionCallback(txHash, 1, 1); // No approval needed, this is the only transaction (1 of 1)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ class OrchidWeb3V0 {

/// Transfer the int amount from the user to the specified lottery pot address.
/// If the total exceeds walletBalance the amount value is automatically reduced.
Future<List<String> /*TransactionId*/ > orchidAddFunds({
Future<void> orchidAddFunds({
required OrchidWallet wallet,
required EthereumAddress signer,
required Token addBalance,
required Token addEscrow,
required ERC20PayableTransactionCallbacks? callbacks,
}) async {
if (wallet.address == null) {
throw Exception("Wallet address is null");
Expand All @@ -55,8 +56,6 @@ class OrchidWeb3V0 {
var totalOXT = Token.min(addBalance.add(addEscrow), walletBalance);
log("Add funds: signer: $signer, amount: ${totalOXT.subtract(addEscrow)}, escrow: $addEscrow");

List<String> txHashes = [];

// Check allowance and skip approval if sufficient.
// function allowance(address owner, address spender) external view returns (uint256)
Token oxtAllowance = await _oxt.getERC20Allowance(
Expand All @@ -69,7 +68,7 @@ class OrchidWeb3V0 {
owner: wallet.address!,
spender: OrchidContractV0.lotteryContractAddressV0,
amount: totalOXT);
txHashes.add(approveTxHash);
callbacks?.onApproval(approveTxHash);
} else {
log("Add funds: oxtAllowance already sufficient: $oxtAllowance");
}
Expand All @@ -87,8 +86,7 @@ class OrchidWeb3V0 {
TransactionOverride(
gasLimit: BigInt.from(OrchidContractV0.gasLimitLotteryPush)),
);
txHashes.add(tx.hash);
return txHashes;
callbacks?.onTransaction(tx.hash);
}

/// Withdraw from balance and escrow to the wallet address.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class OrchidWeb3V1 {

/// Transfer the amount from the user to the specified lottery pot address.
/// If the total exceeds walletBalance the balance value is reduced.
Future<List<String> /*TransactionId*/ > orchidAddFunds({
Future<String> /*TransactionId*/ orchidAddFunds({
required OrchidWallet wallet,
required EthereumAddress signer,
required Token addBalance,
Expand Down Expand Up @@ -64,7 +64,7 @@ class OrchidWeb3V1 {
retrieve: retrieve,
totalPayable: totalPayable,
);
return [tx.hash];
return tx.hash;
}

/// Withdraw funds by moving the specified withdrawEscrow amount from escrow
Expand Down
56 changes: 42 additions & 14 deletions web-ethereum/account_dapp/lib/pages/dapp_add_funds.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:orchid/dapp/orchid_web3/orchid_erc20.dart';
import 'package:orchid/orchid/orchid.dart';
import 'package:orchid/api/orchid_crypto.dart';
import 'package:orchid/api/orchid_eth/token_type.dart';
Expand All @@ -22,11 +23,12 @@ class AddFundsPane extends StatefulWidget {
final tokenType;

// Callback to add the funds
final Future<List<String>> Function({
final Future<void> Function({
required OrchidWallet? wallet,
required EthereumAddress? signer,
required Token addBalance,
required Token addEscrow,
required ERC20PayableTransactionCallbacks? callbacks,
}) addFunds;

const AddFundsPane({
Expand Down Expand Up @@ -100,9 +102,11 @@ class _AddFundsPaneState extends State<AddFundsPane> with DappTabWalletContext {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DappButton(
text: s.addFunds,
onPressed: _addFundsFormEnabled ? _addFunds : null),
DappTransactionButton(
text: s.addFunds,
onPressed: _addFundsFormEnabled ? _addFunds : null,
txPending: txPending,
),
],
),
pady(32),
Expand Down Expand Up @@ -133,7 +137,8 @@ class _AddFundsPaneState extends State<AddFundsPane> with DappTabWalletContext {

bool get _addBalanceFieldValid {
var value = _addBalanceField.value;
return value != null && value <= (walletBalanceOf(tokenType) ?? tokenType.zero);
return value != null &&
value <= (walletBalanceOf(tokenType) ?? tokenType.zero);
}

bool get _addBalanceFieldError {
Expand All @@ -142,7 +147,8 @@ class _AddFundsPaneState extends State<AddFundsPane> with DappTabWalletContext {

bool get _addDepositFieldValid {
var value = _addDepositField.value;
return value != null && value <= (walletBalanceOf(tokenType) ?? tokenType.zero);
return value != null &&
value <= (walletBalanceOf(tokenType) ?? tokenType.zero);
}

bool get _addDepositFieldError {
Expand Down Expand Up @@ -176,29 +182,51 @@ class _AddFundsPaneState extends State<AddFundsPane> with DappTabWalletContext {
_totalAdd.gtZero();
}

// This generic add funds method can be delegated to either the V0 or V1 impls and so must accommodate
// ERC20 token approvals and transactions.
void _addFunds() async {
if (!_addBalanceFieldValid) {
return;
}
setState(() {
txPending = true;
});

final progress = ERC20PayableTransactionCallbacks(
onApprovalCallback: (txHash, seriesIndex, seriesTotal) async {
await UserPreferencesDapp().addTransaction(DappTransaction(
transactionHash: txHash,
chainId: web3Context!.chain.chainId /*always Ethereum*/,
type: DappTransactionType.addFunds,
subtype: "approve",
series_index: seriesIndex,
series_total: seriesTotal,
));
},
onTransactionCallback: (txHash, seriesIndex, seriesTotal) async {
await UserPreferencesDapp().addTransaction(DappTransaction(
transactionHash: txHash,
chainId: web3Context!.chain.chainId,
// always Ethereum
type: DappTransactionType.addFunds,
// TODO: Localize
subtype: "push",
series_index: seriesIndex,
series_total: seriesTotal,
));
},
);

try {
final txHashes = await widget.addFunds(
await widget.addFunds(
wallet: wallet,
signer: widget.signer,
// nulls guarded by _addBalanceFieldValid and _addDepositFieldValid
addBalance: _addBalanceField.value!,
addEscrow: _addDepositField.value!,
callbacks: progress,
);

// Persisting the transaction(s) will update the UI elsewhere.
UserPreferencesDapp().addTransactions(txHashes.map((hash) => DappTransaction(
transactionHash: hash,
chainId: widget.context!.chain.chainId,
type: DappTransactionType.addFunds,
)));

_addBalanceField.clear();
_addDepositField.clear();
setState(() {});
Expand Down
35 changes: 20 additions & 15 deletions web-ethereum/account_dapp/lib/pages/v0/dapp_lock_warn_v0.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class _LockWarnPaneV0State extends State<LockWarnPaneV0> {
pot!.isUnlocking ? s.unlocking : s.locked);
statusText += pot!.isUnlocking
? '\n' +
s.theFundsWillBeAvailableForWithdrawalInTime(pot!.unlockInString())
s.theFundsWillBeAvailableForWithdrawalInTime(
pot!.unlockInString())
: '';
isUnlockedOrUnlocking = (pot!.isUnlocked || pot!.isUnlocking);
}
Expand All @@ -75,21 +76,25 @@ class _LockWarnPaneV0State extends State<LockWarnPaneV0> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (isUnlockedOrUnlocking)
DappButton(
text: s.lockDeposit,
onPressed: _formEnabled()
? () {
_lockOrUnlock(lock: true);
}
: null)
DappTransactionButton(
text: s.lockDeposit,
onPressed: _formEnabled()
? () {
_lockOrUnlock(lock: true);
}
: null,
txPending: _txPending,
)
else
DappButton(
text: s.unlockDeposit,
onPressed: _formEnabled()
? () {
_lockOrUnlock(lock: false);
}
: null),
DappTransactionButton(
text: s.unlockDeposit,
onPressed: _formEnabled()
? () {
_lockOrUnlock(lock: false);
}
: null,
txPending: _txPending,
),
],
),
],
Expand Down
12 changes: 8 additions & 4 deletions web-ethereum/account_dapp/lib/pages/v0/dapp_move_funds_v0.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class MoveFundsPaneV0 extends StatefulWidget {
final bool enabled;

const MoveFundsPaneV0({
Key? key,
Key? key,
required this.context,
required this.pot,
required this.signer,
Expand Down Expand Up @@ -65,8 +65,11 @@ class _MoveFundsPaneV0State extends State<MoveFundsPaneV0> {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DappButton(
text: buttonTitle, onPressed: _formEnabled ? _moveFunds : null),
DappTransactionButton(
text: buttonTitle,
onPressed: _formEnabled ? _moveFunds : null,
txPending: _txPending,
),
],
),
],
Expand Down Expand Up @@ -94,7 +97,8 @@ class _MoveFundsPaneV0State extends State<MoveFundsPaneV0> {
_txPending = true;
});
try {
var txHash = await OrchidWeb3V0(widget.context!).orchidMoveBalanceToEscrow(
var txHash =
await OrchidWeb3V0(widget.context!).orchidMoveBalanceToEscrow(
signer: widget.signer!,
pot: pot!,
moveAmount: _moveBalanceField.value!,
Expand Down
Loading

0 comments on commit bd659d9

Please sign in to comment.