diff --git a/lib/pages/token_view/token_contract_details_view.dart b/lib/pages/token_view/token_contract_details_view.dart new file mode 100644 index 000000000..c73ea6a75 --- /dev/null +++ b/lib/pages/token_view/token_contract_details_view.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.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/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class TokenContractDetailsView extends ConsumerStatefulWidget { + const TokenContractDetailsView({ + Key? key, + required this.contractAddress, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/tokenContractDetailsView"; + + final String contractAddress; + final String walletId; + + @override + ConsumerState createState() => + _TokenContractDetailsViewState(); +} + +class _TokenContractDetailsViewState + extends ConsumerState { + final isDesktop = Util.isDesktop; + + late EthContract contract; + + @override + void initState() { + contract = MainDB.instance.isar.ethContracts + .where() + .addressEqualTo(widget.contractAddress) + .findFirstSync()!; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "Contract details", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (builderContext, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ); + }, + ), + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _Item( + title: "Contract address", + data: contract.address, + button: SimpleCopyButton( + data: contract.address, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Name", + data: contract.name, + button: SimpleCopyButton( + data: contract.name, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Symbol", + data: contract.symbol, + button: SimpleCopyButton( + data: contract.symbol, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Type", + data: contract.type.name, + button: SimpleCopyButton( + data: contract.type.name, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Decimals", + data: contract.decimals.toString(), + button: SimpleCopyButton( + data: contract.decimals.toString(), + ), + ), + ], + ), + ); + } +} + +class _Item extends StatelessWidget { + const _Item({ + Key? key, + required this.title, + required this.data, + required this.button, + }) : super(key: key); + + final String title; + final String data; + final Widget button; + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.itemSubtitle(context), + ), + button, + ], + ), + const SizedBox( + height: 5, + ), + data.isNotEmpty + ? SelectableText( + data, + style: STextStyles.w500_14(context), + ) + : Text( + "$title will appear here", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle3, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/token_view/token_view.dart b/lib/pages/token_view/token_view.dart index 54c8e920c..665b829c3 100644 --- a/lib/pages/token_view/token_view.dart +++ b/lib/pages/token_view/token_view.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart'; import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.dart'; +import 'package:stackwallet/pages/token_view/token_contract_details_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; @@ -15,6 +16,7 @@ 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:tuple/tuple.dart'; final tokenServiceStateProvider = StateProvider((ref) => null); final tokenServiceProvider = ChangeNotifierProvider( @@ -118,7 +120,15 @@ class _TokenViewState extends ConsumerState { Assets.svg.verticalEllipsis, ), onPressed: () { - // todo: contract details + // todo: context menu + Navigator.of(context).pushNamed( + TokenContractDetailsView.routeName, + arguments: Tuple2( + ref.watch(tokenServiceProvider + .select((value) => value!.tokenContract.address)), + widget.walletId, + ), + ); }, ), ], diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 7103d5f6b..a6e69f8d6 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -95,6 +95,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/stack_privacy_calls.dart'; import 'package:stackwallet/pages/token_view/my_tokens_view.dart'; +import 'package:stackwallet/pages/token_view/token_contract_details_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/edit_note_view.dart'; @@ -231,6 +232,21 @@ class RouteGenerator { ), ); + case TokenContractDetailsView.routeName: + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => TokenContractDetailsView( + contractAddress: args.item1, + walletId: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case SingleFieldEditView.routeName: if (args is Tuple2) { return getRoute(