Add tokens class

This commit is contained in:
likho 2023-01-20 19:24:19 +02:00
parent 1c435bb739
commit 706cbbfa39
9 changed files with 2078 additions and 20 deletions

View file

@ -3,6 +3,8 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
@ -22,29 +24,33 @@ import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
class MyTokensView extends ConsumerStatefulWidget { class MyTokensView extends ConsumerStatefulWidget {
const MyTokensView({ const MyTokensView({
Key? key, Key? key,
required this.walletId, required this.walletAddress,
required this.tokens,
required this.walletName,
}) : super(key: key); }) : super(key: key);
static const String routeName = "/myTokens"; static const String routeName = "/myTokens";
final String walletId; final String walletAddress;
final List<dynamic> tokens;
final String walletName;
@override @override
ConsumerState<MyTokensView> createState() => _TokenDetailsViewState(); ConsumerState<MyTokensView> createState() => _TokenDetailsViewState();
} }
class _TokenDetailsViewState extends ConsumerState<MyTokensView> { class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
late final String walletId; late final String walletAddress;
late final TextEditingController _searchController; late final TextEditingController _searchController;
final searchFieldFocusNode = FocusNode(); final searchFieldFocusNode = FocusNode();
@override @override
void initState() { void initState() {
walletId = widget.walletId; walletAddress = widget.walletAddress;
_searchController = TextEditingController(); _searchController = TextEditingController();
super.initState(); super.initState();
@ -95,7 +101,7 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
width: 12, width: 12,
), ),
Text( Text(
"My ETH Wallet Tokens", "${widget.walletName} Tokens",
style: STextStyles.desktopH3(context), style: STextStyles.desktopH3(context),
), ),
], ],
@ -117,7 +123,7 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
}, },
), ),
title: Text( title: Text(
"My ETH Wallet Tokens", "${widget.walletName} Tokens",
style: STextStyles.navBarTitle(context), style: STextStyles.navBarTitle(context),
), ),
actions: [ actions: [
@ -250,12 +256,12 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
], ],
), ),
), ),
if (isDesktop)
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
const SizedBox( Expanded(
height: 8, child: MyTokensList(
tokens: widget.tokens, walletAddress: walletAddress),
), ),
], ],
), ),

View file

@ -0,0 +1,103 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/token_view/token_view.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/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
class MyTokenSelectItem extends ConsumerWidget {
const MyTokenSelectItem(
{Key? key,
required this.walletAddress,
required this.tokenData,
required})
: super(key: key);
final String walletAddress;
final Map<String, String> tokenData;
@override
Widget build(BuildContext context, WidgetRef ref) {
int balance = int.parse(tokenData["balance"] as String);
int tokenDecimals = int.parse(tokenData["decimals"] as String);
final balanceInDecimal = (balance / (pow(10, tokenDecimals)));
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: MaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
key: Key("walletListItemButtonKey_${tokenData["symbol"]}"),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 13),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(Constants.size.circularBorderRadius),
),
onPressed: () {
Navigator.of(context).pushNamed(
TokenView.routeName,
arguments: Tuple2(walletAddress, tokenData["contractAddress"]),
);
},
child: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: Coin.ethereum),
width: 28,
height: 28,
),
const SizedBox(
width: 10,
),
Expanded(
child: Consumer(
builder: (_, ref, __) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Text(
tokenData["name"] as String,
style: STextStyles.titleBold12(context),
),
const Spacer(),
Text(
"$balanceInDecimal ${tokenData["symbol"]}",
style: STextStyles.itemSubtitle(context),
),
],
),
const SizedBox(
height: 1,
),
Row(
children: [
Text(
tokenData["symbol"] as String,
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
const Text("0 USD"),
],
),
],
);
},
),
),
],
),
),
);
}
}

View file

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/my_token_select_item.dart';
class MyTokensList extends StatelessWidget {
const MyTokensList({
Key? key,
required this.tokens,
required this.walletAddress,
}) : super(key: key);
final List<dynamic> tokens;
final String walletAddress;
@override
Widget build(BuildContext context) {
return Consumer(
builder: (_, ref, __) {
print("TOKENS LENGTH IS ${tokens.length}");
return ListView.builder(
itemCount: tokens.length,
itemBuilder: (ctx, index) {
return Padding(
padding: const EdgeInsets.all(4),
child: MyTokenSelectItem(
walletAddress: walletAddress,
tokenData: tokens[index] as Map<String, String>,
),
);
},
);
},
);
}
}

View file

@ -0,0 +1,823 @@
import 'dart:async';
import 'package:decimal/decimal.dart';
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/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
import 'package:stackwallet/pages/receive_view/receive_view.dart';
import 'package:stackwallet/pages/send_view/send_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart';
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.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/providers/ui/unread_notifications_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/coins/manager.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/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.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/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/logger.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/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:tuple/tuple.dart';
/// [eventBus] should only be set during testing
class TokenView extends ConsumerStatefulWidget {
const TokenView({
Key? key,
required this.walletId,
required this.contractAddress,
required this.managerProvider,
this.eventBus,
required this.walletAddress,
}) : super(key: key);
static const String routeName = "/token";
static const double navBarHeight = 65.0;
final String walletId;
final String contractAddress;
final String walletAddress;
final ChangeNotifierProvider<Manager> managerProvider;
final EventBus? eventBus;
@override
ConsumerState<TokenView> createState() => _TokenViewState();
}
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;
final _cnLoadingService = ExchangeDataLoadingService();
@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());
}
}
Widget _buildNetworkIcon(WalletSyncStatus status) {
switch (status) {
case WalletSyncStatus.unableToSync:
return SvgPicture.asset(
Assets.svg.radioProblem,
color: Theme.of(context).extension<StackColors>()!.accentColorRed,
width: 20,
height: 20,
);
case WalletSyncStatus.synced:
return SvgPicture.asset(
Assets.svg.radio,
color: Theme.of(context).extension<StackColors>()!.accentColorGreen,
width: 20,
height: 20,
);
case WalletSyncStatus.syncing:
return SvgPicture.asset(
Assets.svg.radioSyncing,
color: Theme.of(context).extension<StackColors>()!.accentColorYellow,
width: 20,
height: 20,
);
}
}
void _onExchangePressed(BuildContext context) async {
unawaited(_cnLoadingService.loadAll(ref));
final coin = ref.read(managerProvider).coin;
if (coin == Coin.epicCash) {
await showDialog<void>(
context: context,
builder: (_) => const StackOkDialog(
title: "Exchange not available for Epic Cash",
),
);
} else if (coin.name.endsWith("TestNet")) {
await showDialog<void>(
context: context,
builder: (_) => const StackOkDialog(
title: "Exchange not available for test net coins",
),
);
} else {
ref.read(currentExchangeNameStateProvider.state).state =
ChangeNowExchange.exchangeName;
final walletId = ref.read(managerProvider).walletId;
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.estimated;
ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider);
ref.read(exchangeFormStateProvider).exchangeType =
ExchangeRateType.estimated;
final currencies = ref
.read(availableChangeNowCurrenciesProvider)
.currencies
.where((element) =>
element.ticker.toLowerCase() == coin.ticker.toLowerCase());
if (currencies.isNotEmpty) {
ref.read(exchangeFormStateProvider).setCurrencies(
currencies.first,
ref
.read(availableChangeNowCurrenciesProvider)
.currencies
.firstWhere(
(element) =>
element.ticker.toLowerCase() !=
coin.ticker.toLowerCase(),
),
);
}
if (mounted) {
unawaited(
Navigator.of(context).pushNamed(
WalletInitiatedExchangeView.routeName,
arguments: Tuple3(
walletId,
coin,
_loadCNData,
),
),
);
}
}
}
Future<void> attemptAnonymize() async {
bool shouldPop = false;
unawaited(
showDialog(
context: context,
builder: (context) => WillPopScope(
child: const CustomLoadingOverlay(
message: "Anonymizing balance",
eventBus: null,
),
onWillPop: () async => shouldPop,
),
),
);
final firoWallet = ref.read(managerProvider).wallet as FiroWallet;
final publicBalance = await firoWallet.availablePublicBalance();
if (publicBalance <= Decimal.zero) {
shouldPop = true;
if (mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(TokenView.routeName),
);
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "No funds available to anonymize!",
context: context,
),
);
}
return;
}
try {
await firoWallet.anonymizeAllPublicFunds();
shouldPop = true;
if (mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(TokenView.routeName),
);
unawaited(
showFloatingFlushBar(
type: FlushBarType.success,
message: "Anonymize transaction submitted",
context: context,
),
);
}
} catch (e) {
shouldPop = true;
if (mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(TokenView.routeName),
);
await showDialog<dynamic>(
context: context,
builder: (_) => StackOkDialog(
title: "Anonymize all failed",
message: "Reason: $e",
),
);
}
}
}
void _loadCNData() {
// unawaited future
if (ref.read(prefsChangeNotifierProvider).externalCalls) {
_cnLoadingService.loadAll(ref, coin: ref.read(managerProvider).coin);
} else {
Logging.instance.log("User does not want to use external calls",
level: LogLevel.Info);
}
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final coin = ref.watch(managerProvider.select((value) => value.coin));
return WillPopScope(
onWillPop: _onWillPop,
child: Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
_logout();
Navigator.of(context).pop();
},
),
titleSpacing: 0,
title: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
width: 24,
height: 24,
),
const SizedBox(
width: 16,
),
Expanded(
child: Text(
ref.watch(
managerProvider.select((value) => value.walletName)),
style: STextStyles.navBarTitle(context),
overflow: TextOverflow.ellipsis,
),
)
],
),
actions: [
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("tokenViewRadioButton"),
size: 36,
shadows: const [],
color:
Theme.of(context).extension<StackColors>()!.background,
icon: _buildNetworkIcon(_currentSyncStatus),
onPressed: () {
Navigator.of(context).pushNamed(
WalletNetworkSettingsView.routeName,
arguments: Tuple3(
walletId,
_currentSyncStatus,
_currentNodeStatus,
),
);
},
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("tokenViewAlertsButton"),
size: 36,
shadows: const [],
color:
Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
ref.watch(notificationsProvider.select((value) =>
value.hasUnreadNotificationsFor(walletId)))
? Assets.svg.bellNew(context)
: Assets.svg.bell,
width: 20,
height: 20,
color: ref.watch(notificationsProvider.select((value) =>
value.hasUnreadNotificationsFor(walletId)))
? null
: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: () {
// reset unread state
ref.refresh(unreadNotificationsStateProvider);
Navigator.of(context)
.pushNamed(
NotificationsView.routeName,
arguments: walletId,
)
.then((_) {
final Set<int> unreadNotificationIds = ref
.read(unreadNotificationsStateProvider.state)
.state;
if (unreadNotificationIds.isEmpty) return;
List<Future<dynamic>> futures = [];
for (int i = 0;
i < unreadNotificationIds.length - 1;
i++) {
futures.add(ref
.read(notificationsProvider)
.markAsRead(
unreadNotificationIds.elementAt(i), false));
}
// wait for multiple to update if any
Future.wait(futures).then((_) {
// only notify listeners once
ref
.read(notificationsProvider)
.markAsRead(unreadNotificationIds.last, true);
});
});
},
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("tokenViewSettingsButton"),
size: 36,
shadows: const [],
color:
Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.bars,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
debugPrint("wallet view settings tapped");
Navigator.of(context).pushNamed(
WalletSettingsView.routeName,
arguments: Tuple4(
walletId,
ref.read(managerProvider).coin,
_currentSyncStatus,
_currentNodeStatus,
),
);
},
),
),
),
],
),
body: SafeArea(
child: 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: WalletSummary(
walletId: walletId,
managerProvider: managerProvider,
initialSyncStatus: ref.watch(managerProvider
.select((value) => value.isRefreshing))
? WalletSyncStatus.syncing
: WalletSyncStatus.synced,
),
),
),
if (coin == Coin.firo)
const SizedBox(
height: 10,
),
if (coin == Coin.firo)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Expanded(
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
onPressed: () async {
await showDialog<void>(
context: context,
builder: (context) => StackDialog(
title: "Attention!",
message:
"You're about to anonymize all of your public funds.",
leftButton: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
"Cancel",
style: STextStyles.button(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
rightButton: TextButton(
onPressed: () async {
Navigator.of(context).pop();
unawaited(attemptAnonymize());
},
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(
context),
child: Text(
"Continue",
style: STextStyles.button(context),
),
),
),
);
},
child: Text(
"Anonymize funds",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
),
),
),
],
),
),
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Transactions",
style: STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
),
BlueTextButton(
text: "See all",
onTap: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
arguments: walletId,
);
},
),
],
),
),
const SizedBox(
height: 12,
),
Expanded(
child: Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.only(bottom: 14),
child: ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(
Constants.size.circularBorderRadius,
),
bottom: Radius.circular(
// TokenView.navBarHeight / 2.0,
Constants.size.circularBorderRadius,
),
),
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Expanded(
child: TransactionsList(
managerProvider: managerProvider,
walletId: walletId,
),
),
],
),
),
),
),
),
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;
final walletName = ref
.read(managerProvider)
.walletName;
List<dynamic> tokens =
await getWalletTokens(
walletAddress);
await Navigator.of(context).pushNamed(
MyTokensView.routeName,
arguments: Tuple3(walletAddress,
tokens, walletName),
);
},
),
),
),
],
),
],
)
],
),
),
],
),
),
),
),
),
);
}
}

View file

@ -38,6 +38,7 @@ import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
@ -777,10 +778,23 @@ class _WalletViewState extends ConsumerState<WalletView> {
); );
}, },
onBuyPressed: () {}, onBuyPressed: () {},
onTokensPressed: () { onTokensPressed: () async {
Navigator.of(context).pushNamed( final walletAddress = await ref
.read(managerProvider)
.currentReceivingAddress;
final walletName = ref
.read(managerProvider)
.walletName;
List<dynamic> tokens =
await getWalletTokens(
walletAddress);
await Navigator.of(context).pushNamed(
MyTokensView.routeName, MyTokensView.routeName,
arguments: walletId, arguments: Tuple3(walletAddress,
tokens, walletName),
); );
}, },
), ),

View file

@ -79,6 +79,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart';
import 'package:stackwallet/pages/token_view/my_tokens_view.dart'; import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
@ -1294,11 +1295,27 @@ class RouteGenerator {
return _routeError("${settings.name} invalid args: ${args.toString()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
case MyTokensView.routeName: case MyTokensView.routeName:
if (args is String) { if (args is Tuple3<String, List<dynamic>, String>) {
return getRoute( return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute, shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => MyTokensView( builder: (_) => MyTokensView(
walletId: args, walletAddress: args.item1,
tokens: args.item2,
walletName: args.item3),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case TokenView.routeName:
if (args is Tuple2<String, String>) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => TokenView(
walletAddress: args.item1,
contractAddress: args.item2,
), ),
settings: RouteSettings( settings: RouteSettings(
name: settings.name, name: settings.name,

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@ import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
@ -41,8 +42,6 @@ import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/default_nodes.dart';
const int MINIMUM_CONFIRMATIONS = 5; const int MINIMUM_CONFIRMATIONS = 5;
const String GENESIS_HASH_MAINNET =
"0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa";
//THis is used for mapping transactions per address from the block explorer //THis is used for mapping transactions per address from the block explorer
class AddressTransaction { class AddressTransaction {
@ -438,8 +437,6 @@ class EthereumWallet extends CoinServiceAPI {
} }
final feeEstimate = await estimateFeeFor(satoshiAmount, fee); final feeEstimate = await estimateFeeFor(satoshiAmount, fee);
print("FEE ESTIMATE IS $feeEstimate");
print("AMOUNT TO SEND IS $satoshiAmount");
bool isSendAll = false; bool isSendAll = false;
final balance = final balance =

View file

@ -0,0 +1,56 @@
import 'dart:convert';
import 'package:http/http.dart';
class AccountModule {
final String message;
final List<dynamic> result;
final String status;
const AccountModule({
required this.message,
required this.result,
required this.status,
});
factory AccountModule.fromJson(Map<String, dynamic> json) {
return AccountModule(
message: json['message'] as String,
result: json['result'] as List<dynamic>,
status: json['status'] as String,
);
}
}
const _blockExplorer = "https://blockscout.com/eth/mainnet/api?";
Future<AccountModule> fetchAccountModule(String action, String address) async {
final response = await get(Uri.parse(
"${_blockExplorer}module=account&action=$action&address=$address"));
if (response.statusCode == 200) {
return AccountModule.fromJson(
json.decode(response.body) as Map<String, dynamic>);
} else {
throw Exception('Failed to load transactions');
}
}
Future<List<dynamic>> getWalletTokens(String address) async {
AccountModule tokens = await fetchAccountModule("tokenlist", address);
//THIS IS ONLY HARD CODED UNTIL API WORKS AGAIN - TODO REMOVE HARDCODED
return [
{
"balance": "369039500000000000",
"contractAddress": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
"decimals": "18",
"name": "Uniswap",
"symbol": "UNI",
"type": "ERC-20"
}
];
if (tokens.message == "OK") {
return tokens.result as List<String>;
}
return <String>[];
}