WIP token view

This commit is contained in:
julian 2023-02-24 10:23:39 -06:00
parent 057066950e
commit 67fbb6ec5e
4 changed files with 438 additions and 390 deletions

View file

@ -4,13 +4,13 @@ import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
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';
@ -48,23 +48,28 @@ class MyTokenSelectItem extends ConsumerWidget {
borderRadius:
BorderRadius.circular(Constants.size.circularBorderRadius),
),
onPressed: () {
onPressed: () async {
final mnemonicList = ref.read(managerProvider).mnemonic;
final tokenService = EthereumTokenService(
token: token,
walletMnemonic: mnemonicList,
secureStore: ref.read(secureStoreProvider));
secureStore: ref.read(secureStoreProvider),
);
Navigator.of(context).pushNamed(
await showLoading<void>(
whileFuture: tokenService.initializeExisting(),
context: context,
message: "Loading ${token.name}",
);
await Navigator.of(context).pushNamed(
TokenView.routeName,
arguments: Tuple4(
arguments: Tuple3(
walletId,
token,
ref
.read(walletsChangeNotifierProvider)
.getManagerProvider(walletId),
tokenService),
tokenService,
),
);
},

View file

@ -0,0 +1,298 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/trade_card.dart';
import 'package:stackwallet/widgets/transaction_card.dart';
import 'package:tuple/tuple.dart';
class TokenTransactionsList extends ConsumerStatefulWidget {
const TokenTransactionsList({
Key? key,
required this.walletId,
required this.tokenService,
}) : super(key: key);
final String walletId;
final EthereumTokenService tokenService;
@override
ConsumerState<TokenTransactionsList> createState() =>
_TransactionsListState();
}
class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
//
bool _hasLoaded = false;
List<Transaction> _transactions2 = [];
BorderRadius get _borderRadiusFirst {
return BorderRadius.only(
topLeft: Radius.circular(
Constants.size.circularBorderRadius,
),
topRight: Radius.circular(
Constants.size.circularBorderRadius,
),
);
}
BorderRadius get _borderRadiusLast {
return BorderRadius.only(
bottomLeft: Radius.circular(
Constants.size.circularBorderRadius,
),
bottomRight: Radius.circular(
Constants.size.circularBorderRadius,
),
);
}
Widget itemBuilder(
BuildContext context,
Transaction tx,
BorderRadius? radius,
Coin coin,
) {
final matchingTrades = ref
.read(tradesServiceProvider)
.trades
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
final trade = matchingTrades.first;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCard(
// this may mess with combined firo transactions
key: tx.isConfirmed(
ref.watch(walletsChangeNotifierProvider.select((value) =>
value.getManager(widget.walletId).currentHeight)),
coin.requiredConfirmations)
? Key(tx.txid + tx.type.name + tx.address.value.toString())
: UniqueKey(), //
transaction: tx,
walletId: widget.walletId,
),
TradeCard(
// this may mess with combined firo transactions
key: Key(tx.txid +
tx.type.name +
tx.address.value.toString() +
trade.uuid), //
trade: trade,
onTap: () async {
final walletName = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.walletName;
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: TradeDetailsView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
DesktopDialog(
maxHeight: null,
maxWidth: 580,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
bottom: 16,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade details",
style: STextStyles.desktopH3(context),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
),
),
Flexible(
child: TradeDetailsView(
tradeId: trade.tradeId,
transactionIfSentFromStack: tx,
walletName: walletName,
walletId: widget.walletId,
),
),
],
),
),
const RouteSettings(
name: TradeDetailsView.routeName,
),
),
];
},
),
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.tradeId,
tx,
widget.walletId,
walletName,
),
),
);
}
},
)
],
),
);
} else {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCard(
// this may mess with combined firo transactions
key: tx.isConfirmed(
ref.watch(walletsChangeNotifierProvider.select((value) =>
value.getManager(widget.walletId).currentHeight)),
coin.requiredConfirmations)
? Key(tx.txid + tx.type.name + tx.address.value.toString())
: UniqueKey(),
transaction: tx,
walletId: widget.walletId,
),
);
}
}
@override
Widget build(BuildContext context) {
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId)));
return FutureBuilder(
future: widget.tokenService.transaction,
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_transactions2 = snapshot.data!;
_hasLoaded = true;
}
if (!_hasLoaded) {
return Column(
children: const [
Spacer(),
Center(
child: LoadingIndicator(
height: 50,
width: 50,
),
),
Spacer(
flex: 4,
),
],
);
}
if (_transactions2.isEmpty) {
return const NoTransActionsFound();
} else {
_transactions2.sort((a, b) => b.timestamp - a.timestamp);
return RefreshIndicator(
onRefresh: () async {
//todo: check if print needed
// debugPrint("pulled down to refresh on transaction list");
final managerProvider = ref
.read(walletsChangeNotifierProvider)
.getManagerProvider(widget.walletId);
if (!ref.read(managerProvider).isRefreshing) {
unawaited(ref.read(managerProvider).refresh());
}
},
child: Util.isDesktop
? ListView.separated(
itemBuilder: (context, index) {
BorderRadius? radius;
if (_transactions2.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _transactions2.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _transactions2[index];
return itemBuilder(context, tx, radius, manager.coin);
},
separatorBuilder: (context, index) {
return Container(
width: double.infinity,
height: 2,
color: Theme.of(context)
.extension<StackColors>()!
.background,
);
},
itemCount: _transactions2.length,
)
: ListView.builder(
itemCount: _transactions2.length,
itemBuilder: (context, index) {
BorderRadius? radius;
if (_transactions2.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _transactions2.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _transactions2[index];
return itemBuilder(context, tx, radius, manager.coin);
},
),
);
}
},
);
}
}

View file

@ -1,32 +1,19 @@
import 'dart:async';
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/home_view/home_view.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/transaction_filter_provider.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/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
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';
import 'package:stackwallet/widgets/stack_dialog.dart';
/// [eventBus] should only be set during testing
class TokenView extends ConsumerStatefulWidget {
@ -34,7 +21,6 @@ class TokenView extends ConsumerStatefulWidget {
Key? key,
required this.walletId,
required this.token,
required this.managerProvider,
required this.tokenService,
this.eventBus,
}) : super(key: key);
@ -44,7 +30,6 @@ class TokenView extends ConsumerStatefulWidget {
final String walletId;
final EthToken token;
final ChangeNotifierProvider<Manager> managerProvider;
final EthereumTokenService tokenService;
final EventBus? eventBus;
@ -53,158 +38,27 @@ class TokenView extends ConsumerStatefulWidget {
}
class _TokenViewState extends ConsumerState<TokenView> {
late final EventBus eventBus;
late final String walletId;
late final ChangeNotifierProvider<Manager> managerProvider;
late final bool _shouldDisableAutoSyncOnLogOut;
late WalletSyncStatus _currentSyncStatus;
late NodeConnectionStatus _currentNodeStatus;
late StreamSubscription<dynamic> _syncStatusSubscription;
late StreamSubscription<dynamic> _nodeStatusSubscription;
@override
void initState() {
walletId = widget.walletId;
managerProvider = widget.managerProvider;
ref.read(managerProvider).isActiveWallet = true;
if (!ref.read(managerProvider).shouldAutoSync) {
// enable auto sync if it wasn't enabled when loading wallet
ref.read(managerProvider).shouldAutoSync = true;
_shouldDisableAutoSyncOnLogOut = true;
} else {
_shouldDisableAutoSyncOnLogOut = false;
}
ref.read(managerProvider).refresh();
if (ref.read(managerProvider).isRefreshing) {
_currentSyncStatus = WalletSyncStatus.syncing;
_currentNodeStatus = NodeConnectionStatus.connected;
} else {
_currentSyncStatus = WalletSyncStatus.synced;
if (ref.read(managerProvider).isConnected) {
_currentNodeStatus = NodeConnectionStatus.connected;
} else {
_currentNodeStatus = NodeConnectionStatus.disconnected;
_currentSyncStatus = WalletSyncStatus.unableToSync;
}
}
eventBus =
widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance;
_syncStatusSubscription =
eventBus.on<WalletSyncStatusChangedEvent>().listen(
(event) async {
if (event.walletId == widget.walletId) {
// switch (event.newStatus) {
// case WalletSyncStatus.unableToSync:
// break;
// case WalletSyncStatus.synced:
// break;
// case WalletSyncStatus.syncing:
// break;
// }
setState(() {
_currentSyncStatus = event.newStatus;
});
}
},
);
_nodeStatusSubscription =
eventBus.on<NodeConnectionStatusChangedEvent>().listen(
(event) async {
if (event.walletId == widget.walletId) {
// switch (event.newStatus) {
// case NodeConnectionStatus.disconnected:
// break;
// case NodeConnectionStatus.connected:
// break;
// }
setState(() {
_currentNodeStatus = event.newStatus;
});
}
},
);
super.initState();
}
@override
void dispose() {
_nodeStatusSubscription.cancel();
_syncStatusSubscription.cancel();
super.dispose();
}
DateTime? _cachedTime;
Future<bool> _onWillPop() async {
final now = DateTime.now();
const timeout = Duration(milliseconds: 1500);
if (_cachedTime == null || now.difference(_cachedTime!) > timeout) {
_cachedTime = now;
unawaited(showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async {
Navigator.of(context).popUntil(
ModalRoute.withName(HomeView.routeName),
);
_logout();
return false;
},
child: const StackDialog(title: "Tap back again to exit wallet"),
),
).timeout(
timeout,
onTimeout: () => Navigator.of(context).popUntil(
ModalRoute.withName(TokenView.routeName),
),
));
}
return false;
}
void _logout() async {
if (_shouldDisableAutoSyncOnLogOut) {
// disable auto sync if it was enabled only when loading wallet
ref.read(managerProvider).shouldAutoSync = false;
}
ref.read(managerProvider.notifier).isActiveWallet = false;
ref.read(transactionFilterProvider.state).state = null;
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled &&
ref.read(prefsChangeNotifierProvider).backupFrequencyType ==
BackupFrequencyType.afterClosingAWallet) {
unawaited(ref.read(autoSWBServiceProvider).doBackup());
}
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
widget.tokenService.initializeExisting();
// print("MY TOTAL BALANCE IS ${widget.token.totalBalance}");
final coin = ref.watch(managerProvider.select((value) => value.coin));
return WillPopScope(
onWillPop: _onWillPop,
child: Background(
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
_logout();
Navigator.of(context).pop();
},
),
@ -212,7 +66,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
title: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
Assets.svg.iconFor(coin: Coin.ethereum),
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
width: 24,
height: 24,
@ -229,8 +83,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
),
Expanded(
child: Text(
ref.watch(
managerProvider.select((value) => value.coin.ticker)),
widget.token.symbol,
style: STextStyles.navBarTitle(context),
overflow: TextOverflow.ellipsis,
),
@ -238,27 +91,26 @@ class _TokenViewState extends ConsumerState<TokenView> {
],
),
),
body: SafeArea(
child: Container(
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Column(
children: [
const SizedBox(
height: 10,
),
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TokenSummary(
walletId: walletId,
managerProvider: managerProvider,
initialSyncStatus: ref.watch(managerProvider
.select((value) => value.isRefreshing))
? WalletSyncStatus.syncing
: WalletSyncStatus.synced,
),
),
),
// Center(
// child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16),
// child: TokenSummary(
// walletId: widget.walletId,
// managerProvider: managerProvider,
// initialSyncStatus: ref.watch(managerProvider
// .select((value) => value.isRefreshing))
// ? WalletSyncStatus.syncing
// : WalletSyncStatus.synced,
// ),
// ),
// ),
const SizedBox(
height: 20,
),
@ -280,7 +132,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
onTap: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
arguments: walletId,
arguments: widget.walletId,
);
},
),
@ -291,12 +143,8 @@ class _TokenViewState extends ConsumerState<TokenView> {
height: 12,
),
Expanded(
child: Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.only(bottom: 14),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(
@ -315,8 +163,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
),
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: TransactionsList(
@ -330,109 +177,8 @@ class _TokenViewState extends ConsumerState<TokenView> {
),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Padding(
// padding: const EdgeInsets.only(
// bottom: 14,
// left: 16,
// right: 16,
// ),
// child: SizedBox(
// height: TokenView.navBarHeight,
// child: WalletNavigationBar(
// enableExchange:
// Constants.enableExchange &&
// ref.watch(managerProvider.select(
// (value) => value.coin)) !=
// Coin.epicCash,
// height: TokenView.navBarHeight,
// onExchangePressed: () =>
// _onExchangePressed(context),
// onReceivePressed: () async {
// final coin =
// ref.read(managerProvider).coin;
// if (mounted) {
// unawaited(
// Navigator.of(context).pushNamed(
// ReceiveView.routeName,
// arguments: Tuple2(
// walletId,
// coin,
// ),
// ));
// }
// },
// onSendPressed: () {
// final walletId =
// ref.read(managerProvider).walletId;
// final coin =
// ref.read(managerProvider).coin;
// switch (ref
// .read(
// walletBalanceToggleStateProvider
// .state)
// .state) {
// case WalletBalanceToggleState.full:
// ref
// .read(
// publicPrivateBalanceStateProvider
// .state)
// .state = "Public";
// break;
// case WalletBalanceToggleState
// .available:
// ref
// .read(
// publicPrivateBalanceStateProvider
// .state)
// .state = "Private";
// break;
// }
// Navigator.of(context).pushNamed(
// SendView.routeName,
// arguments: Tuple2(
// walletId,
// coin,
// ),
// );
// },
// onBuyPressed: () {},
// onTokensPressed: () async {
// final walletAddress = await ref
// .read(managerProvider)
// .currentReceivingAddress;
//
// List<dynamic> tokens =
// await getWalletTokens(await ref
// .read(managerProvider)
// .currentReceivingAddress);
//
// await Navigator.of(context).pushNamed(
// MyTokensView.routeName,
// arguments: Tuple4(managerProvider,
// walletId, walletAddress, tokens),
// );
// },
// ),
// ),
// ),
],
),
],
)
],
),
),
],
),
),
),
),
),
);

View file

@ -1461,15 +1461,14 @@ class RouteGenerator {
// }
case TokenView.routeName:
if (args is Tuple4<String, EthToken, ChangeNotifierProvider<Manager>,
if (args is Tuple3<String, EthToken,
EthereumTokenService>) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => TokenView(
walletId: args.item1,
token: args.item2,
managerProvider: args.item3,
tokenService: args.item4,
tokenService: args.item3,
),
settings: RouteSettings(
name: settings.name,