Merge pull request #525 from detherminal/staging

feat: implement custom block explorer using isar
This commit is contained in:
julian-CStack 2023-05-08 08:20:29 -06:00 committed by GitHub
commit c864c60e68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 352 additions and 2 deletions

View file

@ -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<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
Navigator.of(context).pushNamed(ChooseCoinView.routeName,
arguments: const Tuple3<String, String, String>("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,
),
],
),
),
),
),
],
),
),

View file

@ -0,0 +1,108 @@
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<ManageExplorerView> createState() => _ManageExplorerViewState();
}
class _ManageExplorerViewState extends ConsumerState<ManageExplorerView> {
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<StackColors>()!
.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<StackColors>()!
.getPrimaryEnabledButtonStyle(context),
onPressed: () {
textEditingController.text = textEditingController.text.trim();
setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)).then((value) =>
Navigator.of(context).pop()
);
},
child: Text(
"Save",
style: STextStyles.button(context),
),
),
),
)
],
),
),
),
);
}
}

View file

@ -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<String, String, String>) {
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,

View file

@ -1,6 +1,12 @@
import 'dart:ffi';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
Uri getBlockExplorerTransactionUrlFor({
import '../db/hive/db.dart';
import '../db/isar/main_db.dart';
import '../models/isar/models/block_explorer.dart';
Uri getDefaultBlockExplorerUrlFor({
required Coin coin,
required String txid,
}) {
@ -18,7 +24,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 +47,23 @@ Uri getBlockExplorerTransactionUrlFor({
return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm");
}
}
Future<int> setBlockExplorerForCoin(
{required Coin coin, required Uri url}
) async {
await MainDB.instance.putTransactionBlockExplorer(TransactionBlockExplorer(ticker: coin.ticker, url: url.toString()));
return 0;
}
Uri getBlockExplorerTransactionUrlFor({
required Coin coin,
required String txid,
}) {
var url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url.toString();
if (url == null) {
return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid);
} else {
url = url.replaceAll("%5BTXID%5D", txid);
return Uri.parse(url);
}
}

View file

@ -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._();

View file

@ -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<ChooseCoinView> createState() => _ChooseCoinViewState();
}
class _ChooseCoinViewState extends ConsumerState<ChooseCoinView> {
List<Coin> _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<Coin> coins = showTestNet
? _coins
: _coins.sublist(0, _coins.length - kTestNetCoinCount);
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.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),
),
],
)
],
),
),
),
),
);
},
),
],
),
),
),
),
);
}
}