mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-02 19:26:37 +00:00
convert token service to change notifier, add token cache per eth wallet, token balances, and fix routing issues
This commit is contained in:
parent
2287cd751e
commit
fcd8f01d93
7 changed files with 197 additions and 156 deletions
71
lib/models/token_balance.dart
Normal file
71
lib/models/token_balance.dart
Normal file
|
@ -0,0 +1,71 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
|
||||
class TokenBalance extends Balance {
|
||||
TokenBalance({
|
||||
required this.contractAddress,
|
||||
required this.decimalPlaces,
|
||||
required super.total,
|
||||
required super.spendable,
|
||||
required super.blockedTotal,
|
||||
required super.pendingSpendable,
|
||||
super.coin = Coin.ethereum,
|
||||
});
|
||||
|
||||
final String contractAddress;
|
||||
final int decimalPlaces;
|
||||
|
||||
@override
|
||||
Decimal getTotal({bool includeBlocked = false}) =>
|
||||
Format.satoshisToEthTokenAmount(
|
||||
includeBlocked ? total : total - blockedTotal,
|
||||
decimalPlaces,
|
||||
);
|
||||
|
||||
@override
|
||||
Decimal getSpendable() => Format.satoshisToEthTokenAmount(
|
||||
spendable,
|
||||
decimalPlaces,
|
||||
);
|
||||
|
||||
@override
|
||||
Decimal getPending() => Format.satoshisToEthTokenAmount(
|
||||
pendingSpendable,
|
||||
decimalPlaces,
|
||||
);
|
||||
|
||||
@override
|
||||
Decimal getBlocked() => Format.satoshisToEthTokenAmount(
|
||||
blockedTotal,
|
||||
decimalPlaces,
|
||||
);
|
||||
|
||||
@override
|
||||
String toJsonIgnoreCoin() => jsonEncode({
|
||||
"decimalPlaces": decimalPlaces,
|
||||
"total": total,
|
||||
"spendable": spendable,
|
||||
"blockedTotal": blockedTotal,
|
||||
"pendingSpendable": pendingSpendable,
|
||||
});
|
||||
|
||||
factory TokenBalance.fromJson(
|
||||
String json,
|
||||
String contractAddress,
|
||||
int decimalPlaces,
|
||||
) {
|
||||
final decoded = jsonDecode(json);
|
||||
return TokenBalance(
|
||||
contractAddress: contractAddress,
|
||||
decimalPlaces: decoded["decimalPlaces"] as int,
|
||||
total: decoded["total"] as int,
|
||||
spendable: decoded["spendable"] as int,
|
||||
blockedTotal: decoded["blockedTotal"] as int,
|
||||
pendingSpendable: decoded["pendingSpendable"] as int,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ import 'package:stackwallet/utilities/format.dart';
|
|||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class MyTokenSelectItem extends ConsumerWidget {
|
||||
const MyTokenSelectItem(
|
||||
|
@ -51,27 +50,25 @@ class MyTokenSelectItem extends ConsumerWidget {
|
|||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
onPressed: () async {
|
||||
final tokenService = EthereumTokenService(
|
||||
ref.read(tokenServiceStateProvider.state).state =
|
||||
EthereumTokenService(
|
||||
token: token,
|
||||
secureStore: ref.read(secureStoreProvider),
|
||||
ethWallet: ref.read(managerProvider).wallet as EthereumWallet,
|
||||
tracker: TransactionNotificationTracker(
|
||||
walletId: ref.read(managerProvider).walletId),
|
||||
walletId: ref.read(managerProvider).walletId,
|
||||
),
|
||||
);
|
||||
|
||||
await showLoading<void>(
|
||||
whileFuture: tokenService.initializeExisting(),
|
||||
whileFuture: ref.read(tokenServiceProvider)!.initializeExisting(),
|
||||
context: context,
|
||||
message: "Loading ${token.name}",
|
||||
);
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
TokenView.routeName,
|
||||
arguments: Tuple3(
|
||||
walletId,
|
||||
token,
|
||||
tokenService,
|
||||
),
|
||||
arguments: walletId,
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -1,127 +1,50 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/pages/token_view/sub_widgets/token_summary_info.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary_info.dart';
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
|
||||
class TokenSummary extends StatelessWidget {
|
||||
class TokenSummary extends ConsumerWidget {
|
||||
const TokenSummary({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.managerProvider,
|
||||
required this.initialSyncStatus,
|
||||
this.aspectRatio = 2.0,
|
||||
this.minHeight = 100.0,
|
||||
this.minWidth = 200.0,
|
||||
this.maxHeight = 250.0,
|
||||
this.maxWidth = 400.0,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final ChangeNotifierProvider<Manager> managerProvider;
|
||||
final WalletSyncStatus initialSyncStatus;
|
||||
|
||||
final double aspectRatio;
|
||||
final double minHeight;
|
||||
final double minWidth;
|
||||
final double maxHeight;
|
||||
final double maxWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: aspectRatio,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: minHeight,
|
||||
minWidth: minWidth,
|
||||
maxHeight: maxHeight,
|
||||
maxWidth: minWidth,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.colorForCoin(ref.watch(
|
||||
managerProvider.select((value) => value.coin))),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Spacer(
|
||||
flex: 5,
|
||||
),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.ellipse1,
|
||||
// fit: BoxFit.fitWidth,
|
||||
// clipBehavior: Clip.none,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 25,
|
||||
),
|
||||
],
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return RoundedContainer(
|
||||
color: const Color(0xFFE9EAFF), // todo: fix color
|
||||
// color: Theme.of(context).extension<StackColors>()!.,
|
||||
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManager(walletId).walletName,
|
||||
),
|
||||
),
|
||||
// Positioned.fill(
|
||||
// child:
|
||||
// Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
// children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(
|
||||
flex: 1,
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.ellipse2,
|
||||
// fit: BoxFit.f,
|
||||
// clipBehavior: Clip.none,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 13,
|
||||
),
|
||||
],
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
Text(
|
||||
ref.watch(tokenServiceProvider.select((value) => value!.balance
|
||||
.getTotal()
|
||||
.toStringAsFixed(ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.token.decimals))))),
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
Text(
|
||||
ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManager(walletId).walletName,
|
||||
),
|
||||
),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
Positioned.fill(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TokenSummaryInfo(
|
||||
walletId: walletId,
|
||||
managerProvider: managerProvider,
|
||||
initialSyncStatus: initialSyncStatus,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'package:event_bus/event_bus.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||
import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
|
||||
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
|
||||
|
@ -15,13 +14,16 @@ import 'package:stackwallet/widgets/background.dart';
|
|||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
|
||||
final tokenServiceStateProvider =
|
||||
StateProvider<EthereumTokenService?>((ref) => null);
|
||||
final tokenServiceProvider = ChangeNotifierProvider<EthereumTokenService?>(
|
||||
(ref) => ref.watch(tokenServiceStateProvider));
|
||||
|
||||
/// [eventBus] should only be set during testing
|
||||
class TokenView extends ConsumerStatefulWidget {
|
||||
const TokenView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.token,
|
||||
required this.tokenService,
|
||||
this.eventBus,
|
||||
}) : super(key: key);
|
||||
|
||||
|
@ -29,8 +31,6 @@ class TokenView extends ConsumerStatefulWidget {
|
|||
static const double navBarHeight = 65.0;
|
||||
|
||||
final String walletId;
|
||||
final EthToken token;
|
||||
final EthereumTokenService tokenService;
|
||||
final EventBus? eventBus;
|
||||
|
||||
@override
|
||||
|
@ -51,7 +51,6 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
// print("MY TOTAL BALANCE IS ${widget.token.totalBalance}");
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
|
@ -67,7 +66,6 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
|||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.iconFor(coin: Coin.ethereum),
|
||||
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
|
@ -76,14 +74,16 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
|||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.token.name,
|
||||
ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.token.name)),
|
||||
style: STextStyles.navBarTitle(context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.token.symbol,
|
||||
ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.token.symbol)),
|
||||
style: STextStyles.navBarTitle(context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
@ -167,7 +167,8 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: TokenTransactionsList(
|
||||
tokenService: widget.tokenService,
|
||||
tokenService: ref.watch(tokenServiceProvider
|
||||
.select((value) => value!)),
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -128,7 +128,6 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/nodes_
|
|||
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/security_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart';
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
||||
|
@ -1461,14 +1460,11 @@ class RouteGenerator {
|
|||
// }
|
||||
|
||||
case TokenView.routeName:
|
||||
if (args is Tuple3<String, EthToken,
|
||||
EthereumTokenService>) {
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => TokenView(
|
||||
walletId: args.item1,
|
||||
token: args.item2,
|
||||
tokenService: args.item3,
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||
|
@ -9,10 +10,12 @@ import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
|||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/models/token_balance.dart';
|
||||
import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart';
|
||||
import 'package:stackwallet/services/ethereum/ethereum_api.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/eth_token_cache.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
|
@ -25,7 +28,7 @@ import 'package:stackwallet/utilities/logger.dart';
|
|||
import 'package:tuple/tuple.dart';
|
||||
import 'package:web3dart/web3dart.dart' as web3dart;
|
||||
|
||||
class EthereumTokenService {
|
||||
class EthereumTokenService extends ChangeNotifier with EthTokenCache {
|
||||
final EthToken token;
|
||||
final EthereumWallet ethWallet;
|
||||
final TransactionNotificationTracker tracker;
|
||||
|
@ -48,11 +51,11 @@ class EthereumTokenService {
|
|||
required this.tracker,
|
||||
}) : _secureStore = secureStore {
|
||||
_contractAddress = web3dart.EthereumAddress.fromHex(token.contractAddress);
|
||||
initCache(ethWallet.walletId, token);
|
||||
}
|
||||
|
||||
Future<Decimal> get availableBalance async {
|
||||
return await totalBalance;
|
||||
}
|
||||
TokenBalance get balance => _balance ??= getCachedBalance();
|
||||
TokenBalance? _balance;
|
||||
|
||||
Coin get coin => Coin.ethereum;
|
||||
|
||||
|
@ -177,18 +180,6 @@ class EthereumTokenService {
|
|||
|
||||
final feeEstimate = await estimateFeeFor(satoshiAmount, fee);
|
||||
|
||||
bool isSendAll = false;
|
||||
final balance =
|
||||
Format.decimalAmountToSatoshis(await availableBalance, coin);
|
||||
if (satoshiAmount == balance) {
|
||||
isSendAll = true;
|
||||
}
|
||||
|
||||
if (isSendAll) {
|
||||
//Send the full balance
|
||||
satoshiAmount = balance;
|
||||
}
|
||||
|
||||
Map<String, dynamic> txData = {
|
||||
"fee": feeEstimate,
|
||||
"feeInWei": fee,
|
||||
|
@ -205,6 +196,7 @@ class EthereumTokenService {
|
|||
if (!_refreshLock) {
|
||||
_refreshLock = true;
|
||||
try {
|
||||
await refreshCachedBalance();
|
||||
await _refreshTransactions();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
@ -213,22 +205,33 @@ class EthereumTokenService {
|
|||
);
|
||||
} finally {
|
||||
_refreshLock = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Decimal> get totalBalance async {
|
||||
Future<void> refreshCachedBalance() async {
|
||||
final balanceRequest = await _client.call(
|
||||
contract: _contract,
|
||||
function: _balanceFunction,
|
||||
params: [_credentials.address]);
|
||||
|
||||
String balance = balanceRequest.first.toString();
|
||||
final balanceInDecimal = Format.satoshisToEthTokenAmount(
|
||||
int.parse(balance),
|
||||
token.decimals,
|
||||
print("==========================================");
|
||||
print("balanceRequest: $balanceRequest");
|
||||
print("==========================================");
|
||||
|
||||
String _balance = balanceRequest.first.toString();
|
||||
|
||||
final newBalance = TokenBalance(
|
||||
contractAddress: token.contractAddress,
|
||||
total: int.parse(_balance),
|
||||
spendable: int.parse(_balance),
|
||||
blockedTotal: 0,
|
||||
pendingSpendable: 0,
|
||||
decimalPlaces: token.decimals,
|
||||
);
|
||||
return Decimal.parse(balanceInDecimal.toString());
|
||||
await updateCachedBalance(newBalance);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<Transaction>> get transactions => ethWallet.db
|
||||
|
|
50
lib/services/mixins/eth_token_cache.dart
Normal file
50
lib/services/mixins/eth_token_cache.dart
Normal file
|
@ -0,0 +1,50 @@
|
|||
import 'package:stackwallet/hive/db.dart';
|
||||
import 'package:stackwallet/models/ethereum/eth_token.dart';
|
||||
import 'package:stackwallet/models/token_balance.dart';
|
||||
|
||||
abstract class _Keys {
|
||||
static String tokenBalance(String contractAddress) {
|
||||
return "tokenBalanceCache_$contractAddress";
|
||||
}
|
||||
}
|
||||
|
||||
mixin EthTokenCache {
|
||||
late final String _walletId;
|
||||
late final EthToken _token;
|
||||
|
||||
void initCache(String walletId, EthToken token) {
|
||||
_walletId = walletId;
|
||||
_token = token;
|
||||
}
|
||||
|
||||
// token balance cache
|
||||
TokenBalance getCachedBalance() {
|
||||
final jsonString = DB.instance.get<dynamic>(
|
||||
boxName: _walletId,
|
||||
key: _Keys.tokenBalance(_token.contractAddress),
|
||||
) as String?;
|
||||
if (jsonString == null) {
|
||||
return TokenBalance(
|
||||
contractAddress: _token.contractAddress,
|
||||
decimalPlaces: _token.decimals,
|
||||
total: 0,
|
||||
spendable: 0,
|
||||
blockedTotal: 0,
|
||||
pendingSpendable: 0,
|
||||
);
|
||||
}
|
||||
return TokenBalance.fromJson(
|
||||
jsonString,
|
||||
_token.contractAddress,
|
||||
_token.decimals,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateCachedBalance(TokenBalance balance) async {
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: _walletId,
|
||||
key: _Keys.tokenBalance(_token.contractAddress),
|
||||
value: balance.toJsonIgnoreCoin(),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue