Customizable block explorer

This commit is contained in:
Hanh 2023-03-14 10:01:03 +10:00
parent e82f9ffa26
commit e7be97ed46
18 changed files with 195 additions and 22 deletions

View File

@ -21,7 +21,6 @@ abstract class CoinBase {
String get currency;
String get ticker;
int get coinIndex;
String get explorerUrl;
AssetImage get image;
String get dbName;
late String dbDir;
@ -30,6 +29,7 @@ abstract class CoinBase {
bool get supportsUA;
bool get supportsMultisig;
List<double> get weights;
List<String> get blockExplorers;
void init(String dbDirPath) {
dbDir = dbDirPath;
@ -70,8 +70,7 @@ abstract class CoinBase {
await File(p.join(dbDir, dbName)).delete();
await File(p.join(dbDir, "$dbName-shm")).delete();
await File(p.join(dbDir, "$dbName-wal")).delete();
}
catch (e) {} // ignore failure
} catch (e) {} // ignore failure
}
String _getFullPath(String dbPath) {

View File

@ -11,7 +11,6 @@ class YcashCoin extends CoinBase {
int coinIndex = 347;
String ticker = "YEC";
String dbName = "yec.db";
String explorerUrl = "https://yecblockexplorer.com/tx/";
AssetImage image = AssetImage('assets/ycash.png');
List<LWInstance> lwd = [
LWInstance("Lightwalletd", "https://lite.ycash.xyz:9067"),
@ -19,4 +18,5 @@ class YcashCoin extends CoinBase {
bool supportsUA = false;
bool supportsMultisig = true;
List<double> weights = [5, 25, 250];
List<String> blockExplorers = ["https://yecblockexplorer.com/tx"];
}

View File

@ -11,7 +11,6 @@ class ZcashCoin extends CoinBase {
int coinIndex = 133;
String ticker = "ZEC";
String dbName = "zec.db";
String explorerUrl = "https://explorer.zcha.in/transactions/";
AssetImage image = AssetImage('assets/zcash.png');
List<LWInstance> lwd = [
LWInstance("Lightwalletd", "https://mainnet.lightwalletd.com:9067"),
@ -20,4 +19,10 @@ class ZcashCoin extends CoinBase {
bool supportsUA = true;
bool supportsMultisig = false;
List<double> weights = [0.05, 0.25, 2.50];
List<String> blockExplorers = [
"https://explorer.zcha.in/transactions",
"https://blockchair.com/zcash/transaction",
"https://zcashblockexplorer.com/transactions",
"https://zecblockexplorer.com/tx"
];
}

View File

@ -11,7 +11,6 @@ class ZcashTestCoin extends CoinBase {
int coinIndex = 133;
String ticker = "ZEC";
String dbName = "zec-test.db";
String explorerUrl = "https://explorer.zcha.in/transactions/";
AssetImage image = AssetImage('assets/zcash.png');
List<LWInstance> lwd = [
LWInstance("Lightwalletd", "https://testnet.lightwalletd.com:9067"),
@ -19,4 +18,5 @@ class ZcashTestCoin extends CoinBase {
bool supportsUA = true;
bool supportsMultisig = false;
List<double> weights = [0.05, 0.25, 2.50];
List<String> blockExplorers = ["https://explorer.zcha.in/transactions"];
}

View File

@ -147,6 +147,7 @@ class MessageLookup extends MessageLookupByLibrary {
"barcodeScannerIsNotAvailableOnDesktop":
MessageLookupByLibrary.simpleMessage(
"Barcode scanner is not available on desktop"),
"blockExplorer": MessageLookupByLibrary.simpleMessage("Block Explorer"),
"blockReorgDetectedRewind": m3,
"blue": MessageLookupByLibrary.simpleMessage("Blue"),
"body": MessageLookupByLibrary.simpleMessage("Body"),

View File

@ -148,6 +148,7 @@ class MessageLookup extends MessageLookupByLibrary {
"barcodeScannerIsNotAvailableOnDesktop":
MessageLookupByLibrary.simpleMessage(
"El escáner de código de barras no está disponible en el escritorio"),
"blockExplorer": MessageLookupByLibrary.simpleMessage("Block Explorer"),
"blockReorgDetectedRewind": m3,
"blue": MessageLookupByLibrary.simpleMessage("Azul"),
"body": MessageLookupByLibrary.simpleMessage("Cuerpo"),

View File

@ -148,6 +148,7 @@ class MessageLookup extends MessageLookupByLibrary {
"barcodeScannerIsNotAvailableOnDesktop":
MessageLookupByLibrary.simpleMessage(
"Le Barcode scanner est seulement disponible sur mobile"),
"blockExplorer": MessageLookupByLibrary.simpleMessage("Block Explorer"),
"blockReorgDetectedRewind": m3,
"blue": MessageLookupByLibrary.simpleMessage("Bleu"),
"body": MessageLookupByLibrary.simpleMessage("Contenu"),

View File

@ -3141,6 +3141,16 @@ class S {
args: [],
);
}
/// `Block Explorer`
String get blockExplorer {
return Intl.message(
'Block Explorer',
name: 'blockExplorer',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@ -307,5 +307,6 @@
"never": "Never",
"always": "Always",
"scanTransparentAddresses": "Scan Transparent Addresses",
"scanningAddresses": "Scanning addresses"
"scanningAddresses": "Scanning addresses",
"blockExplorer": "Block Explorer"
}

View File

@ -305,5 +305,6 @@
"never": "Never",
"always": "Always",
"scanTransparentAddresses": "Scan Transparent Addresses",
"scanningAddresses": "Scanning addresses"
"scanningAddresses": "Scanning addresses",
"blockExplorer": "Block Explorer"
}

View File

@ -306,5 +306,6 @@
"never": "Jamais",
"always": "Toujours",
"scanTransparentAddresses": "Scan Transparent Addresses",
"scanningAddresses": "Scanning addresses"
"scanningAddresses": "Scanning addresses",
"blockExplorer": "Block Explorer"
}

View File

@ -69,6 +69,7 @@ const MAXMONEY = 21000000;
const DOC_URL = "https://hhanh00.github.io/zwallet/";
const APP_NAME = "YWallet";
const BACKUP_NAME = "$APP_NAME.age";
const EXPLORER_KEY = "block_explorer";
const RECOVERY_FILE = "recover.bin";
@ -414,6 +415,8 @@ class ZWalletAppState extends State<ZWalletApp> {
for (var c in coins) {
_setProgress(0.2 * (c.coin + 1), 'Initializing ${c.ticker}');
await compute(_initWallet, {'coin': c, 'passwd': settings.dbPasswd});
if (WarpApi.getProperty(c.coin, EXPLORER_KEY).isEmpty)
WarpApi.setProperty(c.coin, EXPLORER_KEY, c.blockExplorers[0]);
}
_setProgress(0.7, 'Restoring Active Account');

View File

@ -1,5 +1,6 @@
import 'package:YWallet/store.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:warp_api/warp_api.dart';
@ -316,6 +317,20 @@ class SettingsState extends State<SettingsPage>
name: 'memo',
controller: _memoController,
onSaved: _onMemo),
Row(children: [
Expanded(
child: TextField(
controller: TextEditingController(
text: WarpApi.getProperty(
active.coin, EXPLORER_KEY)),
decoration:
InputDecoration(labelText: s.blockExplorer),
readOnly: true,
)),
IconButton(
onPressed: _editBlockExplorer,
icon: Icon(Icons.edit))
]),
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
ButtonBar(children: confirmButtons(context, _onSave))
]))))));
@ -427,6 +442,61 @@ class SettingsState extends State<SettingsPage>
_editTheme() {
Navigator.of(context).pushNamed('/edit_theme');
}
_editBlockExplorer() async {
final selectedKey = 'selected_block_explorer';
final customKey = 'custom_block_explorer';
final s = S.of(context);
final customController = TextEditingController(
text: WarpApi.getProperty(active.coin, customKey));
String selectedExplorer = WarpApi.getProperty(active.coin, selectedKey);
if (selectedExplorer.isEmpty)
selectedExplorer = active.coinDef.blockExplorers[0];
List<FormBuilderFieldOption<String>> options = active.coinDef.blockExplorers
.map((explorer) => FormBuilderFieldOption<String>(
child: Text(explorer), value: explorer))
.toList();
options.add(
FormBuilderFieldOption(value: 'custom', child: Text(s.custom)),
);
final formKey = GlobalKey<FormBuilderState>();
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(s.blockExplorer),
content: FormBuilder(
key: formKey,
child: Column(mainAxisSize: MainAxisSize.min, children: [
FormBuilderRadioGroup(
orientation: OptionsOrientation.vertical,
name: 'block_explorers',
initialValue: selectedExplorer,
options: options),
FormBuilderTextField(
decoration: InputDecoration(labelText: s.custom),
name: 'custom',
controller: customController,
),
])),
actions: confirmButtons(context, () {
Navigator.of(context).pop(true);
}))) ??
false;
if (confirmed) {
final state = formKey.currentState!;
final selection = state.fields['block_explorers']!.value;
final custom = state.fields['custom']!.value;
WarpApi.setProperty(active.coin, selectedKey, selection);
WarpApi.setProperty(active.coin, customKey, custom);
final url = selection == 'custom' ? custom : selection;
WarpApi.setProperty(active.coin, EXPLORER_KEY, url);
setState(() {});
}
}
}
class ServerSelect extends StatefulWidget {

View File

@ -2,6 +2,7 @@ import 'package:YWallet/contact.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:warp_api/data_fb_generated.dart';
import 'package:warp_api/warp_api.dart';
import 'main.dart';
import 'settings.dart';
@ -37,29 +38,45 @@ class TransactionState extends State<TransactionPage> {
ListTile(
title: Text('TXID'), subtitle: SelectableText('${tx.fullTxId}')),
ListTile(
title: Text(S.of(context).height), subtitle: SelectableText('${tx.height}')),
title: Text(S.of(context).height),
subtitle: SelectableText('${tx.height}')),
ListTile(
title: Text(S.of(context).confs), subtitle: SelectableText('${syncStatus.latestHeight - tx.height + 1}')),
title: Text(S.of(context).confs),
subtitle:
SelectableText('${syncStatus.latestHeight - tx.height + 1}')),
ListTile(
title: Text(S.of(context).timestamp),
subtitle: Text('${tx.timestamp}')),
ListTile(title: Text(S.of(context).amount), subtitle: SelectableText(decimalFormat(tx.value, 8))),
ListTile(
title: Text(S.of(context).address), subtitle: SelectableText('${tx.address}'),
trailing: IconButton(icon: Icon(Icons.contacts), onPressed: _addContact)),
title: Text(S.of(context).amount),
subtitle: SelectableText(decimalFormat(tx.value, 8))),
ListTile(
title: Text(S.of(context).contactName), subtitle: SelectableText('${tx.contact ?? "N/A"}')),
ListTile(title: Text(S.of(context).memo), subtitle: SelectableText('${tx.memo}')),
title: Text(S.of(context).address),
subtitle: SelectableText('${tx.address}'),
trailing: IconButton(
icon: Icon(Icons.contacts), onPressed: _addContact)),
ListTile(
title: Text(S.of(context).contactName),
subtitle: SelectableText('${tx.contact ?? "N/A"}')),
ListTile(
title: Text(S.of(context).memo),
subtitle: SelectableText('${tx.memo}')),
ButtonBar(alignment: MainAxisAlignment.center, children: [
IconButton(onPressed: txIndex > 0 ? _prev : null, icon: Icon(Icons.chevron_left)),
ElevatedButton(onPressed: _onOpen, child: Text(S.of(context).openInExplorer)),
IconButton(onPressed: txIndex < n-1 ? _next : null, icon: Icon(Icons.chevron_right)),
IconButton(
onPressed: txIndex > 0 ? _prev : null,
icon: Icon(Icons.chevron_left)),
ElevatedButton(
onPressed: _onOpen, child: Text(S.of(context).openInExplorer)),
IconButton(
onPressed: txIndex < n - 1 ? _next : null,
icon: Icon(Icons.chevron_right)),
]),
]));
}
_onOpen() {
launchUrl(Uri.parse("${activeCoin().explorerUrl}${tx.fullTxId}"));
final url = WarpApi.getProperty(active.coin, EXPLORER_KEY);
launchUrl(Uri.parse("$url/${tx.fullTxId}"));
}
_prev() {

@ -1 +1 @@
Subproject commit a2f6917ce8de03ee6a4ec5a0564163d59f84da9e
Subproject commit dbaa1c02c93bbc8546c66e4478802528a6bb1256

View File

@ -611,6 +611,15 @@ class WarpApi {
return unwrapResultBool(
warp_api_lib.decrypt_db(toNative(dbPath), toNative(passwd)));
}
static String getProperty(int coin, String name) {
return unwrapResultString(warp_api_lib.get_property(coin, toNative(name)));
}
static void setProperty(int coin, String name, String value) {
unwrapResultU8(
warp_api_lib.set_property(coin, toNative(name), toNative(value)));
}
}
String signOnlyIsolateFn(SignOnlyParams params) {

View File

@ -1350,6 +1350,38 @@ class NativeLibrary {
late final _dart_clone_db_with_passwd _clone_db_with_passwd =
_clone_db_with_passwd_ptr.asFunction<_dart_clone_db_with_passwd>();
CResult_____c_char get_property(
int coin,
ffi.Pointer<ffi.Int8> name,
) {
return _get_property(
coin,
name,
);
}
late final _get_property_ptr =
_lookup<ffi.NativeFunction<_c_get_property>>('get_property');
late final _dart_get_property _get_property =
_get_property_ptr.asFunction<_dart_get_property>();
CResult_u8 set_property(
int coin,
ffi.Pointer<ffi.Int8> name,
ffi.Pointer<ffi.Int8> value,
) {
return _set_property(
coin,
name,
value,
);
}
late final _set_property_ptr =
_lookup<ffi.NativeFunction<_c_set_property>>('set_property');
late final _dart_set_property _set_property =
_set_property_ptr.asFunction<_dart_set_property>();
int has_cuda() {
return _has_cuda();
}
@ -2467,6 +2499,28 @@ typedef _dart_clone_db_with_passwd = CResult_u8 Function(
ffi.Pointer<ffi.Int8> passwd,
);
typedef _c_get_property = CResult_____c_char Function(
ffi.Uint8 coin,
ffi.Pointer<ffi.Int8> name,
);
typedef _dart_get_property = CResult_____c_char Function(
int coin,
ffi.Pointer<ffi.Int8> name,
);
typedef _c_set_property = CResult_u8 Function(
ffi.Uint8 coin,
ffi.Pointer<ffi.Int8> name,
ffi.Pointer<ffi.Int8> value,
);
typedef _dart_set_property = CResult_u8 Function(
int coin,
ffi.Pointer<ffi.Int8> name,
ffi.Pointer<ffi.Int8> value,
);
typedef _c_has_cuda = ffi.Int8 Function();
typedef _dart_has_cuda = int Function();

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.3.6+411
version: 1.3.6+412
environment:
sdk: ">=2.12.0 <3.0.0"