From 695d43bbd5116d182bb90c739b38d133e0ca35e2 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 28 Feb 2023 13:26:17 -0600 Subject: [PATCH] add token list ui --- lib/models/ethereum/eth_token.dart | 11 +++ .../add_token_view/add_token_view.dart | 84 ++++++++++++----- .../sub_widgets/add_token_list.dart | 85 +++++++++++++++++ .../sub_widgets/add_token_list_element.dart | 94 +++++++++++++++++++ .../sub_widgets/add_token_text.dart | 49 ++++++++++ .../verify_recovery_phrase_view.dart | 1 + lib/pages/token_view/my_tokens_view.dart | 1 + lib/route_generator.dart | 14 ++- 8 files changed, 311 insertions(+), 28 deletions(-) create mode 100644 lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart create mode 100644 lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart create mode 100644 lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart diff --git a/lib/models/ethereum/eth_token.dart b/lib/models/ethereum/eth_token.dart index 748fac307..00351a77b 100644 --- a/lib/models/ethereum/eth_token.dart +++ b/lib/models/ethereum/eth_token.dart @@ -12,4 +12,15 @@ class EthToken { final String symbol; final int decimals; final int balance; + + @override + String toString() { + return "$runtimeType: { " + "name: $name, " + "symbol: $symbol, " + "contractAddress: $contractAddress, " + "decimals: $decimals, " + "balance: $balance" + " }"; + } } diff --git a/lib/pages/add_wallet_views/add_token_view/add_token_view.dart b/lib/pages/add_wallet_views/add_token_view/add_token_view.dart index 85df2a3b6..131a37f82 100644 --- a/lib/pages/add_wallet_views/add_token_view/add_token_view.dart +++ b/lib/pages/add_wallet_views/add_token_view/add_token_view.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; -import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart'; -import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_entity_list.dart'; -import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_text.dart'; -import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/next_button.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_eth_tokens.dart'; @@ -17,6 +16,7 @@ import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -25,8 +25,11 @@ import 'package:stackwallet/widgets/textfield_icon_button.dart'; class AddTokenView extends ConsumerStatefulWidget { const AddTokenView({ Key? key, + required this.walletId, }) : super(key: key); + final String walletId; + static const routeName = "/addToken"; @override @@ -39,36 +42,42 @@ class _AddTokenViewState extends ConsumerState { String _searchTerm = ""; - final List tokenEntities = []; + final List tokenEntities = []; final bool isDesktop = Util.isDesktop; - List filter( + List filter( String text, - List entities, + List entities, ) { final _entities = [...entities]; if (text.isNotEmpty) { final lowercaseTerm = text.toLowerCase(); _entities.retainWhere( (e) => - e.ticker.toLowerCase().contains(lowercaseTerm) || - e.name.toLowerCase().contains(lowercaseTerm) || - (e is EthTokenEntity && - e.token.contractAddress.toLowerCase().contains(lowercaseTerm)), + e.token.name.toLowerCase().contains(lowercaseTerm) || + e.token.symbol.toLowerCase().contains(lowercaseTerm) || + e.token.contractAddress.toLowerCase().contains(lowercaseTerm), ); } return _entities; } + void onNextPressed() { + final selectedTokens = + tokenEntities.where((e) => e.selected).map((e) => e.token); + print("SELECTED TOKENS: $selectedTokens"); + } + @override void initState() { _searchFieldController = TextEditingController(); _searchFocusNode = FocusNode(); - tokenEntities.addAll(DefaultTokens.list.map((e) => EthTokenEntity(e))); - tokenEntities.sort((a, b) => a.name.compareTo(b.name)); + tokenEntities + .addAll(DefaultTokens.list.map((e) => AddTokenListElementData(e))); + tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name)); super.initState(); } @@ -83,6 +92,8 @@ class _AddTokenViewState extends ConsumerState { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + final walletName = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).walletName)); if (isDesktop) { return DesktopScaffold( @@ -93,8 +104,9 @@ class _AddTokenViewState extends ConsumerState { ), body: Column( children: [ - const AddWalletText( + AddTokenText( isDesktop: true, + walletName: walletName, ), const SizedBox( height: 16, @@ -183,8 +195,8 @@ class _AddTokenViewState extends ConsumerState { ), ), Expanded( - child: AddWalletEntityList( - entities: filter(_searchTerm, tokenEntities), + child: AddTokenList( + items: filter(_searchTerm, tokenEntities), ), ), ], @@ -195,11 +207,12 @@ class _AddTokenViewState extends ConsumerState { const SizedBox( height: 16, ), - const SizedBox( + SizedBox( height: 70, width: 480, - child: AddWalletNextButton( - isDesktop: true, + child: PrimaryButton( + label: "Next", + onPressed: onNextPressed, ), ), const SizedBox( @@ -219,6 +232,25 @@ class _AddTokenViewState extends ConsumerState { Navigator.of(context).pop(); }, ), + actions: [ + AspectRatio( + aspectRatio: 1, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // todo add custom token + }, + ), + ), + ), + ], ), body: Container( color: Theme.of(context).extension()!.background, @@ -227,8 +259,9 @@ class _AddTokenViewState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const AddWalletText( + AddTokenText( isDesktop: false, + walletName: walletName, ), const SizedBox( height: 16, @@ -289,15 +322,16 @@ class _AddTokenViewState extends ConsumerState { height: 10, ), Expanded( - child: AddWalletEntityList( - entities: filter(_searchTerm, tokenEntities), + child: AddTokenList( + items: filter(_searchTerm, tokenEntities), ), ), const SizedBox( height: 16, ), - const AddWalletNextButton( - isDesktop: false, + PrimaryButton( + label: "Next", + onPressed: onNextPressed, ), ], ), diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart new file mode 100644 index 000000000..1fd0c13d2 --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; + +class AddTokenList extends StatelessWidget { + const AddTokenList({ + Key? key, + required this.items, + }) : super(key: key); + + final List items; + + @override + Widget build(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + primary: false, + itemCount: items.length, + itemBuilder: (ctx, index) { + return ConditionalParent( + condition: index == items.length - 1, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + child, + Padding( + padding: const EdgeInsets.all(4), + child: RawMaterialButton( + fillColor: + Theme.of(context).extension()!.popupBG, + elevation: 0, + focusElevation: 0, + hoverElevation: 0, + highlightElevation: 0, + constraints: const BoxConstraints(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + // todo add custom token + }, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.circlePlusFilled, + color: Theme.of(context) + .extension()! + .textDark, + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Text( + "Add custom token", + style: STextStyles.w600_14(context), + ), + ], + ), + ), + ), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: AddTokenListElement( + data: items[index], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart new file mode 100644 index 000000000..ecf19fa85 --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/ethereum/eth_token.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.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/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class AddTokenListElementData { + AddTokenListElementData(this.token); + + final EthToken token; + bool selected = false; +} + +class AddTokenListElement extends StatefulWidget { + const AddTokenListElement({Key? key, required this.data}) : super(key: key); + + final AddTokenListElementData data; + + @override + State createState() => _AddTokenListElementState(); +} + +class _AddTokenListElementState extends State { + @override + Widget build(BuildContext context) { + final currency = ExchangeDataLoadingService.instance.isar.currencies + .where() + .tickerExchangeNameEqualToAnyName( + "${widget.data.token.symbol}ERC20", + ChangeNowExchange.exchangeName, + ) + .or() + .tickerExchangeNameEqualToAnyName( + widget.data.token.symbol, + ChangeNowExchange.exchangeName, + ) + .or() + .tickerExchangeNameEqualToAnyName( + "${widget.data.token.symbol}BSC", + ChangeNowExchange.exchangeName, + ) + .filter() + .imageIsNotEmpty() + .findFirstSync(); + return RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + currency != null + ? SvgPicture.network( + currency.image, + width: 24, + height: 24, + ) + : Container( + width: 24, + height: 24, + color: Colors.red, + ), + const SizedBox( + width: 12, + ), + Text( + "${widget.data.token.name} (${widget.data.token.symbol})", + style: STextStyles.w600_14(context), + overflow: TextOverflow.ellipsis, + ), + ], + ), + const SizedBox( + width: 4, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: widget.data.selected, + onValueChanged: (newValue) { + widget.data.selected = newValue; + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart new file mode 100644 index 000000000..c11e6295a --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class AddTokenText extends StatelessWidget { + const AddTokenText({ + Key? key, + required this.isDesktop, + required this.walletName, + }) : super(key: key); + + final String walletName; + final bool isDesktop; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + walletName, + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.sectionLabelMedium12(context) // todo: fixme + : STextStyles.sectionLabelMedium12(context), + ), + const SizedBox( + height: 4, + ), + Text( + "Add Tokens", + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "You can also do it later in your wallet", + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopSubtitleH2(context) + : STextStyles.subtitle(context), + ), + ], + ); + } +} diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart index cc1dc5d20..64324fb20 100644 --- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart @@ -113,6 +113,7 @@ class _VerifyRecoveryPhraseViewState unawaited( Navigator.of(context).pushNamed( AddTokenView.routeName, + arguments: widget.manager.walletId, ), ); } diff --git a/lib/pages/token_view/my_tokens_view.dart b/lib/pages/token_view/my_tokens_view.dart index e8c489043..fc89b5037 100644 --- a/lib/pages/token_view/my_tokens_view.dart +++ b/lib/pages/token_view/my_tokens_view.dart @@ -148,6 +148,7 @@ class _TokenDetailsViewState extends ConsumerState { onPressed: () { Navigator.of(context).pushNamed( AddTokenView.routeName, + arguments: widget.walletId, ); }, ), diff --git a/lib/route_generator.dart b/lib/route_generator.dart index ff9018ce9..9f7dece46 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -205,10 +205,18 @@ class RouteGenerator { settings: RouteSettings(name: settings.name)); case AddTokenView.routeName: - return getRoute( + if (args is String) { + return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => const AddTokenView(), - settings: RouteSettings(name: settings.name)); + builder: (_) => AddTokenView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); case PaynymClaimView.routeName: if (args is String) {