mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 09:47:37 +00:00
Merge pull request #525 from detherminal/staging
feat: implement custom block explorer using isar
This commit is contained in:
commit
c864c60e68
6 changed files with 352 additions and 2 deletions
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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._();
|
||||
|
|
144
lib/widgets/choose_coin_view.dart
Normal file
144
lib/widgets/choose_coin_view.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue