Customizable block explorer
This commit is contained in:
parent
e82f9ffa26
commit
e7be97ed46
|
@ -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) {
|
||||
|
|
|
@ -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"];
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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"];
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -307,5 +307,6 @@
|
|||
"never": "Never",
|
||||
"always": "Always",
|
||||
"scanTransparentAddresses": "Scan Transparent Addresses",
|
||||
"scanningAddresses": "Scanning addresses"
|
||||
"scanningAddresses": "Scanning addresses",
|
||||
"blockExplorer": "Block Explorer"
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"never": "Never",
|
||||
"always": "Always",
|
||||
"scanTransparentAddresses": "Scan Transparent Addresses",
|
||||
"scanningAddresses": "Scanning addresses"
|
||||
"scanningAddresses": "Scanning addresses",
|
||||
"blockExplorer": "Block Explorer"
|
||||
}
|
||||
|
|
|
@ -306,5 +306,6 @@
|
|||
"never": "Jamais",
|
||||
"always": "Toujours",
|
||||
"scanTransparentAddresses": "Scan Transparent Addresses",
|
||||
"scanningAddresses": "Scanning addresses"
|
||||
"scanningAddresses": "Scanning addresses",
|
||||
"blockExplorer": "Block Explorer"
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue