diff --git a/lib/config/dependencies.dart b/lib/config/dependencies.dart index 6beaefe7..7282cf42 100644 --- a/lib/config/dependencies.dart +++ b/lib/config/dependencies.dart @@ -1,5 +1,4 @@ import 'package:get_it/get_it.dart'; -import 'package:sidesail/app.dart'; import 'package:sidesail/config/sidechains.dart'; import 'package:sidesail/providers/balance_provider.dart'; import 'package:sidesail/providers/transactions_provider.dart'; @@ -13,21 +12,15 @@ import 'package:sidesail/storage/secure_store.dart'; // register all global dependencies, for use in views, or in view models // each dependency can only be registered once -Future initGetitDependencies(Sidechain chain) async { +Future initGetitDependencies(Sidechain initialChain) async { final mainFuture = MainchainRPCLive.create(); - final ethFuture = EthereumRPCLive.create(); - await setSidechainRPC(chain); + await _initSidechainRPC(initialChain); final mainRPC = await mainFuture; - final ethRPC = await ethFuture; GetIt.I.registerLazySingleton( () => mainRPC, ); - GetIt.I.registerLazySingleton( - () => ethRPC, - ); - GetIt.I.registerLazySingleton( () => AppRouter(), ); @@ -46,31 +39,51 @@ Future initGetitDependencies(Sidechain chain) async { ); } -// register all global dependencies, for use in views, or in view models -// each dependency can only be registered once -Future setSidechainRPC(Sidechain chain) async { +// register all rpc connections. We attempt to create all +// rpcs in parallell, so they're ready instantly when swapping +// we can also query the balance +Future _initSidechainRPC(Sidechain chain) async { + final ethFuture = EthereumRPCLive.create(); + final testFuture = TestchainRPCLive.create(); + + final ethRPC = await ethFuture; + final testRPC = await testFuture; + + GetIt.I.registerLazySingleton( + () => testRPC, + ); + GetIt.I.registerLazySingleton( + () => ethRPC, + ); + SidechainRPC sidechainRPC; switch (chain.type) { case SidechainType.testChain: - final testchainRPC = await TestchainRPCLive.create(); - if (GetIt.I.isRegistered()) { - GetIt.I.unregister(); - } - GetIt.I.registerLazySingleton( - () => testchainRPC, - ); - sidechainRPC = testchainRPC; + sidechainRPC = testRPC; break; case SidechainType.ethereum: - final ethRPC = await EthereumRPCLive.create(); - if (GetIt.I.isRegistered()) { - GetIt.I.unregister(); - } - GetIt.I.registerLazySingleton( - () => ethRPC, - ); + sidechainRPC = ethRPC; + break; + } + + GetIt.I.registerLazySingleton( + () => sidechainRPC, + ); +} +// swap out the SidechainRPC type in GetIt with the appropriate +// rpc. All of them are already registered, so this is pretty quick +void swapSidechainRPC(Sidechain chain) async { + SidechainRPC sidechainRPC; + switch (chain.type) { + case SidechainType.testChain: + final testchainRPC = GetIt.I.get(); + sidechainRPC = testchainRPC; + break; + + case SidechainType.ethereum: + final ethRPC = GetIt.I.get(); sidechainRPC = ethRPC; break; } @@ -81,6 +94,4 @@ Future setSidechainRPC(Sidechain chain) async { GetIt.I.registerLazySingleton( () => sidechainRPC, ); - - SailApp.sailAppKey.currentState?.rebuildUI(); } diff --git a/lib/pages/tabs/ethereum/ethereum_rpc_tab_page.dart b/lib/pages/tabs/ethereum/ethereum_rpc_tab_page.dart index bd9de730..fc1d1c59 100644 --- a/lib/pages/tabs/ethereum/ethereum_rpc_tab_page.dart +++ b/lib/pages/tabs/ethereum/ethereum_rpc_tab_page.dart @@ -16,8 +16,7 @@ class EthereumRPCTabPage extends StatelessWidget { return const SailPage( scrollable: true, title: 'Ethereum RPC', - subtitle: - 'Here you can call eth rpcs directly to the eth-sidechain. Try typing in "eth_blockNumber" in the input below.', + subtitle: 'Call RPCs directly to the Ethereum sidechain. Try typing in "eth_blockNumber" in the input below.', body: Padding( padding: EdgeInsets.only(bottom: SailStyleValues.padding30), child: Column( diff --git a/lib/pages/tabs/home_page.dart b/lib/pages/tabs/home_page.dart index 8eafa805..67afbaa4 100644 --- a/lib/pages/tabs/home_page.dart +++ b/lib/pages/tabs/home_page.dart @@ -16,20 +16,32 @@ import 'package:stacked/stacked.dart'; @RoutePage() class HomePage extends StatelessWidget { - SidechainRPC get _sideRPC => GetIt.I.get(); - const HomePage({super.key}); @override Widget build(BuildContext context) { - final routes = routesForChain(_sideRPC.chain); final theme = SailTheme.of(context); + const routes = [ + // common routes + SidechainExplorerTabRoute(), + // testchain routes + DashboardTabRoute(), + TransferMainchainTabRoute(), + WithdrawalBundleTabRoute(), + BlindMergedMiningTabRoute(), + + // ethereum routes + EthereumRPCTabRoute(), + + // trailing common routes + NodeSettingsTabRoute(), + ThemeSettingsTabRoute(), + ]; return auto_router.AutoTabsRouter.builder( homeIndex: 1, routes: routes, - builder: (context, children, _) { - final tabsRouter = auto_router.AutoTabsRouter.of(context); + builder: (context, children, tabsRouter) { return Scaffold( backgroundColor: theme.colors.background, body: SideNav( @@ -41,40 +53,6 @@ class HomePage extends StatelessWidget { }, ); } - - List> routesForChain(Sidechain chain) { - final preRoutes = [ - const SidechainExplorerTabRoute(), - ]; - final postRoutes = [ - const NodeSettingsTabRoute(), - const ThemeSettingsTabRoute(), - ]; - - List> chainRoutes = []; - switch (chain.type) { - case SidechainType.testChain: - chainRoutes = [ - const DashboardTabRoute(), - const TransferMainchainTabRoute(), - const WithdrawalBundleTabRoute(), - const BlindMergedMiningTabRoute(), - ]; - break; - - case SidechainType.ethereum: - chainRoutes = [ - const EthereumRPCTabRoute(), - ]; - break; - } - - return [ - ...preRoutes, - ...chainRoutes, - ...postRoutes, - ]; - } } class SideNav extends StatefulWidget { @@ -222,9 +200,9 @@ class _SideNavState extends State { NavEntry( title: '${viewModel.chain.name} Dashboard', icon: SailSVGAsset.iconDashboardTab, - selected: tabsRouter.activeIndex == 1, + selected: tabsRouter.activeIndex == 5, onPressed: () { - tabsRouter.setActiveIndex(1); + tabsRouter.setActiveIndex(5); }, ), ]; diff --git a/lib/pages/tabs/sidechain_explorer_tab_page.dart b/lib/pages/tabs/sidechain_explorer_tab_page.dart index 2394be12..9272eb51 100644 --- a/lib/pages/tabs/sidechain_explorer_tab_page.dart +++ b/lib/pages/tabs/sidechain_explorer_tab_page.dart @@ -4,13 +4,18 @@ import 'package:get_it/get_it.dart'; import 'package:sail_ui/sail_ui.dart'; import 'package:sidesail/config/sidechains.dart'; import 'package:sidesail/providers/balance_provider.dart'; +import 'package:sidesail/rpc/rpc_ethereum.dart'; import 'package:sidesail/rpc/rpc_sidechain.dart'; +import 'package:sidesail/rpc/rpc_testchain.dart'; import 'package:sidesail/widgets/containers/chain_overview_card.dart'; import 'package:sidesail/widgets/containers/tabs/dashboard_tab_widgets.dart'; import 'package:stacked/stacked.dart'; @RoutePage() class SidechainExplorerTabPage extends StatelessWidget { + TestchainRPC get test => GetIt.I.get(); + EthereumRPC get eth => GetIt.I.get(); + const SidechainExplorerTabPage({super.key}); @override @@ -36,9 +41,7 @@ class SidechainExplorerTabPage extends StatelessWidget { unconfirmedBalance: viewModel.pendingBalance, highlighted: false, currentChain: viewModel.chain.type == SidechainType.testChain, - onPressed: () { - viewModel.setSidechainRPC(TestSidechain()); - }, + onPressed: () => viewModel.setSidechainRPC(TestSidechain()), ), ChainOverviewCard( chain: EthereumSidechain(), @@ -46,9 +49,7 @@ class SidechainExplorerTabPage extends StatelessWidget { unconfirmedBalance: viewModel.pendingBalance, highlighted: false, currentChain: viewModel.chain.type == SidechainType.ethereum, - onPressed: () { - viewModel.setSidechainRPC(EthereumSidechain()); - }, + onPressed: () => viewModel.setSidechainRPC(EthereumSidechain()), ), ], ), @@ -83,6 +84,7 @@ class SidechainExplorerTabViewModel extends BaseViewModel { void setSidechainRPC(Sidechain chain) { _sideRPC.setChain(chain); + notifyListeners(); } @override diff --git a/lib/pages/tabs/testchain/mainchain/withdrawal_bundle_tab_page.dart b/lib/pages/tabs/testchain/mainchain/withdrawal_bundle_tab_page.dart index ae7179d6..2bc49627 100644 --- a/lib/pages/tabs/testchain/mainchain/withdrawal_bundle_tab_page.dart +++ b/lib/pages/tabs/testchain/mainchain/withdrawal_bundle_tab_page.dart @@ -283,6 +283,7 @@ class _UnbundledWithdrawalViewState extends State { ), ), copyable: false, + label: '', value: '${satoshiToBTC(widget.withdrawal.amountSatoshi).toStringAsFixed(8)} BTC to ${widget.withdrawal.address}', ), diff --git a/lib/providers/balance_provider.dart b/lib/providers/balance_provider.dart index bd3b02a1..47d2d5fe 100644 --- a/lib/providers/balance_provider.dart +++ b/lib/providers/balance_provider.dart @@ -20,6 +20,7 @@ class BalanceProvider extends ChangeNotifier { BalanceProvider() { fetch(); _startPolling(); + _rpc.addListener(fetch); } // call this function from anywhere to refresh the balance @@ -45,5 +46,6 @@ class BalanceProvider extends ChangeNotifier { super.dispose(); // Cancel timer when provider is disposed (never?) _timer.cancel(); + _rpc.removeListener(notifyListeners); } } diff --git a/lib/routing/router.gr.dart b/lib/routing/router.gr.dart index ceae0fdb..cb674d0d 100644 --- a/lib/routing/router.gr.dart +++ b/lib/routing/router.gr.dart @@ -36,7 +36,7 @@ abstract class _$AppRouter extends RootStackRouter { HomeRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, - child: const HomePage(), + child: HomePage(), ); }, NodeSettingsTabRoute.name: (routeData) { diff --git a/lib/rpc/rpc_ethereum.dart b/lib/rpc/rpc_ethereum.dart index a889a5b9..75670bca 100644 --- a/lib/rpc/rpc_ethereum.dart +++ b/lib/rpc/rpc_ethereum.dart @@ -68,6 +68,7 @@ class EthereumRPCLive extends EthereumRPC { return EthereumAddress.fromHex(accounts[0] as String); } + // ignore: unused_element Future _deposit(int amountSat, int feeSat) async { final amount = sgweiPerSat * amountSat; final fee = sgweiPerSat * feeSat; diff --git a/lib/rpc/rpc_sidechain.dart b/lib/rpc/rpc_sidechain.dart index c36cde3f..6ed15db2 100644 --- a/lib/rpc/rpc_sidechain.dart +++ b/lib/rpc/rpc_sidechain.dart @@ -15,7 +15,7 @@ abstract class SidechainRPC extends RPCConnection { late Sidechain chain; void setChain(Sidechain newChain) { chain = newChain; - setSidechainRPC(newChain); + swapSidechainRPC(newChain); notifyListeners(); } } diff --git a/lib/widgets/containers/tabs/console.dart b/lib/widgets/containers/tabs/console.dart index 37001fb5..887b1d1d 100644 --- a/lib/widgets/containers/tabs/console.dart +++ b/lib/widgets/containers/tabs/console.dart @@ -7,6 +7,7 @@ import 'package:sail_ui/theme/theme.dart'; import 'package:sail_ui/widgets/core/sail_text.dart'; import 'package:sidesail/logger.dart'; import 'package:sidesail/rpc/rpc_ethereum.dart'; +import 'package:sidesail/widgets/containers/tabs/dashboard_tab_widgets.dart'; class RPCWidget extends StatefulWidget { const RPCWidget({super.key}); @@ -15,11 +16,24 @@ class RPCWidget extends StatefulWidget { RPCWidgetState createState() => RPCWidgetState(); } +class Result { + late int id; + final String command; + final String? success; + final String? error; + + Result({ + required this.command, + required this.success, + required this.error, + }) { + id = UniqueKey().hashCode; + } +} + class RPCWidgetState extends State { EthereumRPC get rpc => GetIt.I.get(); - dynamic _result; - String _command = ''; - String? _error; + List results = []; Future _callRpc(String args) async { if (args.trim().isEmpty) { @@ -50,18 +64,26 @@ class RPCWidgetState extends State { try { var res = await _callRpc(selection); - setState(() { - _command = selection; - _result = res; - _error = null; - }); + results.insert( + 0, + Result( + command: selection, + success: res.toString(), + error: null, + ), + ); } catch (e) { - setState(() { - _command = selection; - _result = null; - _error = e.toString(); - }); + results.insert( + 0, + Result( + command: selection, + success: null, + error: e.toString(), + ), + ); } + + setState(() {}); } @override @@ -135,86 +157,49 @@ class RPCWidgetState extends State { ), ], ), - if (_result != null) _JSONView(_result), - if (_error != null) _ErrorView('$_command: $_error'), + DashboardGroup( + title: 'Responses', + children: [ + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: results.length, + itemBuilder: (context, index) => ResultView( + key: ValueKey(results[index].id), + result: results[index], + ), + ), + ], + ), ], ); } } -class _JSONView extends StatelessWidget { - final dynamic json; +class ResultView extends StatelessWidget { + final Result result; - const _JSONView(this.json); - - String prettyPrintJson(dynamic json) { - JsonEncoder encoder = const JsonEncoder.withIndent(' '); - var printed = encoder.convert(json); - return printed; - } + const ResultView({super.key, required this.result}); @override Widget build(BuildContext context) { - return SailRawCard( - padding: true, - child: SailColumn( - spacing: SailStyleValues.padding50, - mainAxisSize: MainAxisSize.min, - children: [ - SailColumn( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: SailStyleValues.padding08, - withDivider: true, - trailingSpacing: true, - children: [ - const ActionHeaderChip(title: 'Response'), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: SailStyleValues.padding10, - ), - child: SailText.primary12(json.toString()), - ), - ], - ), - ], + return Padding( + padding: const EdgeInsets.symmetric( + vertical: SailStyleValues.padding15, + horizontal: SailStyleValues.padding10, ), - ); - } -} - -class _ErrorView extends StatelessWidget { - final String error; - - const _ErrorView(this.error); - - @override - Widget build(BuildContext context) { - return SailRawCard( - padding: true, child: SailColumn( - spacing: SailStyleValues.padding50, - mainAxisSize: MainAxisSize.min, + spacing: SailStyleValues.padding08, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SailColumn( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: SailStyleValues.padding08, - withDivider: true, - trailingSpacing: true, - children: [ - const SailErrorShadow( - enabled: true, - small: true, - child: ActionHeaderChip(title: 'Error'), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: SailStyleValues.padding10, - ), - child: SailText.primary12(error), - ), - ], + SingleValueContainer( + width: 95, + icon: result.error == null + ? SailSVG.icon(SailSVGAsset.iconConfirmed, width: 13) + : SailSVG.icon(SailSVGAsset.iconFailed, width: 13), + copyable: true, + label: result.command.split(' ').first, + value: result.error ?? result.success, ), ], ), diff --git a/packages/sail_ui/lib/widgets/containers/single_value_container.dart b/packages/sail_ui/lib/widgets/containers/single_value_container.dart index 8a8b873d..356b9467 100644 --- a/packages/sail_ui/lib/widgets/containers/single_value_container.dart +++ b/packages/sail_ui/lib/widgets/containers/single_value_container.dart @@ -5,7 +5,7 @@ import 'package:sail_ui/widgets/core/sail_snackbar.dart'; import 'package:sail_ui/widgets/core/sail_text.dart'; class SingleValueContainer extends StatelessWidget { - final String? label; + final String label; final dynamic value; final double width; final String? trailingText; @@ -14,7 +14,7 @@ class SingleValueContainer extends StatelessWidget { const SingleValueContainer({ super.key, - this.label, + required this.label, required this.value, required this.width, this.trailingText, @@ -33,21 +33,21 @@ class SingleValueContainer extends StatelessWidget { const SizedBox( width: 13, ), - if (label != null) - SizedBox( - width: width, - child: SailText.secondary12(label!), + SizedBox( + width: width, + child: SailText.secondary12(label), + ), + Expanded( + child: SailScaleButton( + onPressed: copyable + ? () { + Clipboard.setData(ClipboardData(text: value.toString())); + showSnackBar(context, 'Copied $label'); + } + : null, + child: SailText.primary12(value.toString()), ), - SailScaleButton( - onPressed: copyable - ? () { - Clipboard.setData(ClipboardData(text: value.toString())); - showSnackBar(context, 'Copied $label'); - } - : null, - child: SailText.primary12(value.toString()), ), - Expanded(child: Container()), if (trailingText != null) SailText.secondary12(trailingText!), ], );