From 1587b5b1e9232b5c484eba6f8920a84d2226f50b Mon Sep 17 00:00:00 2001 From: detherminal <76167420+detherminal@users.noreply.github.com> Date: Thu, 4 May 2023 21:10:37 +0300 Subject: [PATCH 1/2] feat: implement custom block explorer --- .../advanced_settings_view.dart | 38 +++++ .../advanced_views/manage_explorer_view.dart | 107 +++++++++++++ lib/route_generator.dart | 32 ++++ lib/utilities/block_explorers.dart | 36 ++++- lib/utilities/prefs.dart | 2 + lib/widgets/choose_coin_view.dart | 144 ++++++++++++++++++ 6 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart create mode 100644 lib/widgets/choose_coin_view.dart diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart index a19ed8668..db52f39a9 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; @@ -10,6 +11,9 @@ import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; + +import 'manage_explorer_view.dart'; class AdvancedSettingsView extends StatelessWidget { const AdvancedSettingsView({ @@ -221,6 +225,40 @@ class AdvancedSettingsView extends StatelessWidget { }, ), ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + Navigator.of(context).pushNamed(ChooseCoinView.routeName, + arguments: const Tuple3("Manage block explorers", "block explorer", ManageExplorerView.routeName)); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Change block explorer", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + ), + ), + ), ], ), ), diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart new file mode 100644 index 000000000..1eb48a757 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/utilities/block_explorers.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/utilities/enums/coin_enum.dart'; + +import '../../../../widgets/rounded_white_container.dart'; + +class ManageExplorerView extends ConsumerStatefulWidget { + const ManageExplorerView({ + Key? key, + required this.coin, + }) : super(key: key); + + static const String routeName = "/manageExplorer"; + + final Coin coin; + + @override + ConsumerState createState() => _ManageExplorerViewState(); +} + +class _ManageExplorerViewState extends ConsumerState { + + + late TextEditingController textEditingController; + + + @override + void initState() { + super.initState(); + textEditingController = TextEditingController(text: getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]").toString().replaceAll("%5BTXID%5D", "[TXID]")); + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()! + .background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "${widget.coin.prettyName} block explorer", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Expanded(child: Column( + children: [ + TextField( + controller: textEditingController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 8,), + RoundedWhiteContainer( + child: Center( + child: Text( + "Edit your block explorer above. Keep in mind that every block explorer has a slightly different URL scheme.\n\n" + "Paste in your block explorer of choice, then edit in [TXID] where the transaction ID should go, and Stack Wallet will auto fill the transaction ID in that place of URL.", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + ], + )), + Align( + alignment: Alignment.bottomCenter, + child: ConstrainedBox(constraints: const BoxConstraints( + minWidth: 480, + minHeight: 70, + ), + child: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () { + textEditingController.text = textEditingController.text.trim(); + setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)); + Navigator.of(context).pop(); + }, + child: Text( + "Save", + style: STextStyles.button(context), + ), + ), + ), + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/route_generator.dart b/lib/route_generator.dart index a5bf762af..9ee8ded15 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -63,6 +63,7 @@ import 'package:stackwallet/pages/send_view/token_send_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; @@ -99,6 +100,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/rename_wallet_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/widgets/choose_coin_view.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'; @@ -206,6 +208,36 @@ class RouteGenerator { builder: (_) => const StackPrivacyCalls(isSettings: false), settings: RouteSettings(name: settings.name)); + case ChooseCoinView.routeName: + if (args is Tuple3) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ChooseCoinView( + title: args.item1, + coinAdditional: args.item2, + nextRouteName: args.item3, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ManageExplorerView.routeName: + if (args is Coin) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ManageExplorerView( + coin: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case WalletsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index c628dbbb4..8130626b8 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -1,6 +1,10 @@ +import 'dart:ffi'; + import 'package:stackwallet/utilities/enums/coin_enum.dart'; -Uri getBlockExplorerTransactionUrlFor({ +import '../db/hive/db.dart'; + +Uri getDefaultBlockExplorerUrlFor({ required Coin coin, required String txid, }) { @@ -18,7 +22,7 @@ Uri getBlockExplorerTransactionUrlFor({ case Coin.dogecoinTestNet: return Uri.parse("https://chain.so/tx/DOGETEST/$txid"); case Coin.epicCash: - // TODO: Handle this case. + // TODO: Handle this case. throw UnimplementedError("missing block explorer for epic cash"); case Coin.ethereum: return Uri.parse("https://etherscan.io/tx/$txid"); @@ -41,3 +45,31 @@ Uri getBlockExplorerTransactionUrlFor({ return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm"); } } + +int setBlockExplorerForCoin( + {required Coin coin, required Uri url} + ) { + var ticker = coin.ticker; + DB.instance.put( + boxName: DB.boxNameAllWalletsData, + key: "${ticker}blockExplorerUrl", + value: url); + return 0; +} + +Uri getBlockExplorerTransactionUrlFor({ + required Coin coin, + required String txid, +}) { + var ticker = coin.ticker; + var url = DB.instance.get( + boxName: DB.boxNameAllWalletsData, + key: "${ticker}blockExplorerUrl", + ); + if (url == null) { + return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid); + } else { + url = url.replace("%5BTXID%5D", txid); + return Uri.parse(url.toString()); + } +} diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 973541366..ea4720b17 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -7,6 +7,8 @@ import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:uuid/uuid.dart'; +import 'enums/coin_enum.dart'; + class Prefs extends ChangeNotifier { Prefs._(); static final Prefs _instance = Prefs._(); diff --git a/lib/widgets/choose_coin_view.dart b/lib/widgets/choose_coin_view.dart new file mode 100644 index 000000000..b2e314f7c --- /dev/null +++ b/lib/widgets/choose_coin_view.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart'; +import 'package:stackwallet/providers/providers.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/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/rounded_white_container.dart'; + +class ChooseCoinView extends ConsumerStatefulWidget { + const ChooseCoinView({ + Key? key, + required this.title, + required this.coinAdditional, + required this.nextRouteName, + }) : super(key: key); + + static const String routeName = "/chooseCoin"; + + final String title; + final String coinAdditional; + final String nextRouteName; + + @override + ConsumerState createState() => _ChooseCoinViewState(); +} + +class _ChooseCoinViewState extends ConsumerState { + List _coins = [...Coin.values]; + + @override + void initState() { + _coins = _coins.toList(); + _coins.remove(Coin.firoTestNet); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool showTestNet = ref.watch( + prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), + ); + + List coins = showTestNet + ? _coins + : _coins.sublist(0, _coins.length - kTestNetCoinCount); + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + widget.title ?? "Choose Coin", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ...coins.map( + (coin) { + final count = ref + .watch(nodeServiceChangeNotifierProvider + .select((value) => value.getNodesFor(coin))) + .length; + + return Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + onPressed: () { + Navigator.of(context).pushNamed( + widget.nextRouteName, + arguments: coin, + ); + }, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${coin.prettyName} ${widget.coinAdditional ?? ""}", + style: STextStyles.titleBold12(context), + ), + ], + ) + ], + ), + ), + ), + ), + ); + }, + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file From 47df2a8a7ac9c206996b5352cf734bdb03ccd902 Mon Sep 17 00:00:00 2001 From: detherminal <76167420+detherminal@users.noreply.github.com> Date: Fri, 5 May 2023 20:30:22 +0300 Subject: [PATCH 2/2] feat: use isar instead of hive --- .../advanced_views/manage_explorer_view.dart | 5 +++-- lib/utilities/block_explorers.dart | 22 +++++++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart index 1eb48a757..dc84fe8c8 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart @@ -88,8 +88,9 @@ class _ManageExplorerViewState extends ConsumerState { .getPrimaryEnabledButtonStyle(context), onPressed: () { textEditingController.text = textEditingController.text.trim(); - setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)); - Navigator.of(context).pop(); + setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)).then((value) => + Navigator.of(context).pop() + ); }, child: Text( "Save", diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index 8130626b8..ebfee9db0 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -3,6 +3,8 @@ import 'dart:ffi'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import '../db/hive/db.dart'; +import '../db/isar/main_db.dart'; +import '../models/isar/models/block_explorer.dart'; Uri getDefaultBlockExplorerUrlFor({ required Coin coin, @@ -46,14 +48,10 @@ Uri getDefaultBlockExplorerUrlFor({ } } -int setBlockExplorerForCoin( +Future setBlockExplorerForCoin( {required Coin coin, required Uri url} - ) { - var ticker = coin.ticker; - DB.instance.put( - boxName: DB.boxNameAllWalletsData, - key: "${ticker}blockExplorerUrl", - value: url); + ) async { + await MainDB.instance.putTransactionBlockExplorer(TransactionBlockExplorer(ticker: coin.ticker, url: url.toString())); return 0; } @@ -61,15 +59,11 @@ Uri getBlockExplorerTransactionUrlFor({ required Coin coin, required String txid, }) { - var ticker = coin.ticker; - var url = DB.instance.get( - boxName: DB.boxNameAllWalletsData, - key: "${ticker}blockExplorerUrl", - ); + var url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url.toString(); if (url == null) { return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid); } else { - url = url.replace("%5BTXID%5D", txid); - return Uri.parse(url.toString()); + url = url.replaceAll("%5BTXID%5D", txid); + return Uri.parse(url); } }