Unification YCash/ZCash
This commit is contained in:
parent
439a552d30
commit
66b1face96
|
@ -54,5 +54,5 @@ jniLibs/
|
|||
ic_launcher.png
|
||||
Icon-App-*.png
|
||||
*.apk
|
||||
assets/icon.png
|
||||
lib/coin/coindef.dart
|
||||
|
||||
Cargo.lock
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -41,7 +41,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "me.hanh.zwallet"
|
||||
applicationId "me.hanh.zywallet"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="me.hanh.zwallet">
|
||||
package="me.hanh.zywallet">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="me.hanh.zwallet">
|
||||
package="me.hanh.zywallet">
|
||||
<application
|
||||
android:label="ZWallet"
|
||||
android:label="ZYWallet"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
@ -22,6 +22,13 @@
|
|||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:scheme="ycash"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package me.hanh.zwallet
|
||||
package me.hanh.zywallet
|
||||
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
|
@ -1,5 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="me.hanh.zwallet">
|
||||
package="me.hanh.zywallet">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
30
configure.sh
30
configure.sh
|
@ -1,34 +1,4 @@
|
|||
export COIN=$1
|
||||
|
||||
case $COIN in
|
||||
ycash)
|
||||
export APP_TITLE=YWallet
|
||||
export APP_NAME=ywallet
|
||||
;;
|
||||
ycashtest)
|
||||
export APP_TITLE=YWalletTest
|
||||
export APP_NAME=ywallettest
|
||||
;;
|
||||
zcashtest)
|
||||
export APP_TITLE=ZWalletTest
|
||||
export APP_NAME=zwallettest
|
||||
;;
|
||||
zcash)
|
||||
export APP_TITLE=ZWallet
|
||||
export APP_NAME=zwallet
|
||||
;;
|
||||
esac
|
||||
|
||||
cp assets/$COIN.png assets/icon.png
|
||||
cp lib/coin/$COIN.dart lib/coin/coindef.dart
|
||||
cp native/zcash-params/src/coindef/$COIN.rs native/zcash-params/src/coin.rs
|
||||
|
||||
mo pubspec.yaml.tpl > pubspec.yaml
|
||||
mo ios/Runner/Info.plist.tpl > ios/Runner/Info.plist
|
||||
|
||||
flutter pub get
|
||||
flutter pub run change_app_package_name:main me.hanh.$APP_NAME
|
||||
mo android/app/src/main/AndroidManifest.xml.tpl > android/app/src/main/AndroidManifest.xml
|
||||
flutter pub run flutter_launcher_icons:main
|
||||
flutter pub run flutter_app_name
|
||||
flutter pub run build_runner build
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
adb uninstall me.hanh.zywallet
|
||||
bundletool build-apks --overwrite --bundle=build/app/outputs/bundle/release/app-release.aab --output=/tmp/app.apks --ks=docker/zwallet.jks --ks-key-alias=hanh --ks-pass=pass:$JKS_PASSWORD
|
||||
bundletool install-apks --adb=/usr/bin/adb --apks=/tmp/app.apks
|
|
@ -11,7 +11,7 @@ import 'generated/l10n.dart';
|
|||
Future<void> showAbout(BuildContext context) async {
|
||||
final contentTemplate = await rootBundle.loadString('assets/about.md');
|
||||
final template = Template(contentTemplate);
|
||||
var content = template.renderString({'APP': coin.app});
|
||||
var content = template.renderString({'APP': APP_NAME});
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
String version = packageInfo.version;
|
||||
String code = packageInfo.buildNumber;
|
||||
|
@ -20,7 +20,7 @@ Future<void> showAbout(BuildContext context) async {
|
|||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('${S.of(context).about} ${coin.app}'),
|
||||
title: Text('${S.of(context).about} $APP_NAME'),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
|
|
913
lib/account.dart
913
lib/account.dart
|
@ -1,376 +1,144 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:sensors_plus/sensors_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:warp/store.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
|
||||
import 'about.dart';
|
||||
import 'budget.dart';
|
||||
import 'chart.dart';
|
||||
import 'contact.dart';
|
||||
import 'history.dart';
|
||||
import 'main.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'note.dart';
|
||||
import 'main.dart';
|
||||
import 'store.dart';
|
||||
|
||||
class AccountPage extends StatefulWidget {
|
||||
class AccountPage2 extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => _AccountPageState();
|
||||
_AccountState createState() => _AccountState();
|
||||
}
|
||||
|
||||
class _AccountPageState extends State<AccountPage>
|
||||
with
|
||||
WidgetsBindingObserver,
|
||||
AutomaticKeepAliveClientMixin,
|
||||
SingleTickerProviderStateMixin {
|
||||
Timer? _timerSync;
|
||||
int _progress = 0;
|
||||
bool _useSnapAddress = false;
|
||||
String _snapAddress = "";
|
||||
late TabController _tabController;
|
||||
bool _accountTab = true;
|
||||
bool _contactsTab = false;
|
||||
StreamSubscription? _progressDispose;
|
||||
StreamSubscription? _syncDispose;
|
||||
StreamSubscription? _accDispose;
|
||||
final contactKey = GlobalKey<ContactsState>();
|
||||
bool _flat = false;
|
||||
|
||||
class _AccountState extends State<AccountPage2> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: settings.simpleMode ? 3 : 6, vsync: this);
|
||||
_tabController.addListener(() {
|
||||
setState(() {
|
||||
_accountTab = _tabController.index == 0;
|
||||
_contactsTab = _tabController.index == (settings.simpleMode ? 2: 5);
|
||||
});
|
||||
});
|
||||
Future.microtask(() async {
|
||||
await accountManager.updateUnconfirmedBalance();
|
||||
await accountManager.fetchAccountData(false);
|
||||
await contacts.fetchContacts();
|
||||
await _setupTimer();
|
||||
});
|
||||
WidgetsBinding.instance?.addObserver(this);
|
||||
_progressDispose = progressStream.listen((percent) {
|
||||
setState(() {
|
||||
_progress = percent;
|
||||
});
|
||||
});
|
||||
_syncDispose = syncStream.listen((height) {
|
||||
setState(() {
|
||||
if (height >= 0) {
|
||||
syncStatus.setSyncHeight(height);
|
||||
eta.checkpoint(height, DateTime.now());
|
||||
} else {
|
||||
WarpApi.mempoolReset(syncStatus.latestHeight);
|
||||
_trySync();
|
||||
}
|
||||
});
|
||||
});
|
||||
_accDispose = accelerometerEvents.listen(_handleAccel);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timerSync?.cancel();
|
||||
WidgetsBinding.instance?.removeObserver(this);
|
||||
_progressDispose?.cancel();
|
||||
_syncDispose?.cancel();
|
||||
_accDispose?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.paused:
|
||||
_timerSync?.cancel();
|
||||
_timerSync = null;
|
||||
_accDispose?.cancel();
|
||||
_accDispose = null;
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
if (_timerSync == null) _setupTimer();
|
||||
if (_accDispose == null)
|
||||
_accDispose = accelerometerEvents.listen(_handleAccel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool get wantKeepAlive => true; //Set to true
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
var s = S.of(context);
|
||||
if (!syncStatus.isSynced() && !syncStatus.syncing) _trySync();
|
||||
final theme = Theme.of(this.context);
|
||||
final hasTAddr = accountManager.taddress.isNotEmpty;
|
||||
final qrSize = getScreenSize(context) / 2.5;
|
||||
final hasMultisign = accountManager.canPay || accountManager.active.share != null;
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Observer(builder: (context) {
|
||||
final _1 = active.dataEpoch;
|
||||
return Column(children: [
|
||||
SyncStatusWidget(),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
QRAddressWidget(),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
BalanceWidget(),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
MemPoolWidget(),
|
||||
ProgressWidget(),
|
||||
]);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class SyncStatusWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final simpleMode = settings.simpleMode;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Observer(
|
||||
builder: (context) => Text("${accountManager.active.name}")),
|
||||
bottom: TabBar(controller: _tabController, isScrollable: true, tabs: [
|
||||
Tab(text: s.account),
|
||||
if (!simpleMode) Tab(text: s.notes),
|
||||
Tab(text: s.history),
|
||||
if (!simpleMode) Tab(text: s.budget),
|
||||
if (!simpleMode) Tab(text: s.tradingPl),
|
||||
Tab(text: s.contacts),
|
||||
]),
|
||||
actions: [
|
||||
Observer(builder: (context) {
|
||||
accountManager.canPay;
|
||||
return PopupMenuButton<String>(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child: Text(s.accounts), value: "Accounts"),
|
||||
PopupMenuItem(
|
||||
child: Text(s.backup), value: "Backup"),
|
||||
PopupMenuItem(
|
||||
child: Text(s.rescan), value: "Rescan"),
|
||||
if (!simpleMode && accountManager.canPay)
|
||||
PopupMenuItem(
|
||||
child: Text(s.coldStorage), value: "Cold"),
|
||||
if (!simpleMode)
|
||||
PopupMenuItem(
|
||||
child: Text(s.multipay), value: "MultiPay"),
|
||||
if (!simpleMode)
|
||||
PopupMenuItem(
|
||||
child: Text(s.broadcast), value: "Broadcast"),
|
||||
if (!simpleMode && coin.supportsMultisig && hasMultisign) PopupMenuItem(
|
||||
child: Text(s.multisig), value: "Multisig"),
|
||||
PopupMenuItem(
|
||||
child: Text(s.settings), value: "Settings"),
|
||||
PopupMenuItem(child: Text(s.help), value: "Help"),
|
||||
PopupMenuItem(child: Text(s.about), value: "About"),
|
||||
// PopupMenuItem(child: Text(s.reset), value: "Reset"),
|
||||
// PopupMenuItem(child: Text("Reorg"), value: "Reorg"),
|
||||
];
|
||||
},
|
||||
onSelected: _onMenu,
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
body: TabBarView(controller: _tabController, children: [
|
||||
SingleChildScrollView(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Center(
|
||||
child: Column(children: [
|
||||
if (simpleMode) Text("Simple Mode"),
|
||||
Observer(builder: (context) {
|
||||
final share = accountManager.active.share;
|
||||
return share != null ? Text("MULTISIG ${share.index}/${share.participants}") : SizedBox();
|
||||
}),
|
||||
Observer(builder: (context) {
|
||||
final _1 = eta.eta;
|
||||
final _2 = syncStatus.syncedHeight;
|
||||
final _3 = syncStatus.latestHeight;
|
||||
return syncStatus.syncedHeight < 0
|
||||
? Text(s.rescanNeeded)
|
||||
: syncStatus.isSynced()
|
||||
? Text('${syncStatus.syncedHeight}',
|
||||
style: theme.textTheme.caption)
|
||||
: Text(
|
||||
'${syncStatus.syncedHeight} / ${syncStatus.latestHeight} ${eta.eta}',
|
||||
style: theme.textTheme.caption!
|
||||
.apply(color: theme.primaryColor));
|
||||
}),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
Observer(builder: (context) {
|
||||
final _ = accountManager.active.address;
|
||||
final address = _address();
|
||||
final shortAddress = addressLeftTrim(address);
|
||||
final showTAddr = accountManager.showTAddr;
|
||||
final hide = settings.autoHide && _flat;
|
||||
return Column(children: [
|
||||
if (hasTAddr)
|
||||
Text(showTAddr
|
||||
? s.tapQrCodeForShieldedAddress
|
||||
: s.tapQrCodeForTransparentAddress),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
GestureDetector(
|
||||
onTap: hasTAddr ? _onQRTap : null,
|
||||
child: RotatedBox(
|
||||
quarterTurns: hide ? 2 : 0,
|
||||
child: QrImage(
|
||||
data: address,
|
||||
size: qrSize,
|
||||
embeddedImage: AssetImage('assets/icon.png'),
|
||||
backgroundColor: Colors.white))),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
RichText(
|
||||
text: TextSpan(children: [
|
||||
TextSpan(
|
||||
text: '$shortAddress ',
|
||||
style: theme.textTheme.bodyText2),
|
||||
WidgetSpan(
|
||||
child: GestureDetector(
|
||||
child: Icon(Icons.content_copy),
|
||||
onTap: _onAddressCopy)),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4))),
|
||||
WidgetSpan(
|
||||
child: GestureDetector(
|
||||
child: Icon(MdiIcons.qrcodeScan),
|
||||
onTap: _onReceive)),
|
||||
])),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
if (!simpleMode && !showTAddr)
|
||||
OutlinedButton(
|
||||
child: Text(s.newSnapAddress),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1, color: theme.primaryColor)),
|
||||
onPressed: _onSnapAddress),
|
||||
if (!simpleMode && showTAddr)
|
||||
OutlinedButton(
|
||||
child: Text(s.shieldTranspBalance),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side:
|
||||
BorderSide(width: 1, color: theme.primaryColor)),
|
||||
onPressed: () { shieldTAddr(context); },
|
||||
)
|
||||
]);
|
||||
}),
|
||||
Observer(builder: (context) {
|
||||
final showTAddr = accountManager.showTAddr;
|
||||
final balance = showTAddr
|
||||
? accountManager.tbalance
|
||||
: accountManager.balance;
|
||||
final hide = settings.autoHide && _flat;
|
||||
final balanceColor = !showTAddr
|
||||
? theme.colorScheme.primaryVariant
|
||||
: theme.colorScheme.secondaryVariant;
|
||||
final balanceHi = hide ? '-------' : _getBalance_hi(balance);
|
||||
final deviceWidth = getWidth(context);
|
||||
final digits = deviceWidth.index < DeviceWidth.sm.index ? 7 : 9;
|
||||
final balanceStyle = (balanceHi.length > digits
|
||||
? theme.textTheme.headline4
|
||||
: theme.textTheme.headline2)!
|
||||
.copyWith(color: balanceColor);
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: <Widget>[
|
||||
if (!hide)
|
||||
Text('${coin.symbol}',
|
||||
style: theme.textTheme.headline5),
|
||||
Text(' $balanceHi', style: balanceStyle),
|
||||
if (!hide) Text('${_getBalance_lo(balance)}'),
|
||||
]);
|
||||
}),
|
||||
Observer(builder: (context) {
|
||||
final hide = settings.autoHide && _flat;
|
||||
final balance = accountManager.showTAddr
|
||||
? accountManager.tbalance
|
||||
: accountManager.balance;
|
||||
final fx = _fx();
|
||||
final balanceFX = balance * fx / ZECUNIT;
|
||||
return hide
|
||||
? Text(s.tiltYourDeviceUpToRevealYourBalance)
|
||||
: Column(children: [
|
||||
if (fx != 0.0)
|
||||
Text(
|
||||
"${decimalFormat(balanceFX, 2, symbol: settings.currency)}",
|
||||
style: theme.textTheme.headline6),
|
||||
if (fx != 0.0)
|
||||
Text(
|
||||
"1 ${coin.ticker} = ${decimalFormat(fx, 2, symbol: settings.currency)}"),
|
||||
]);
|
||||
}),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
Observer(
|
||||
builder: (context) => (accountManager.unconfirmedBalance != 0)
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'${_sign(accountManager.unconfirmedBalance)} ${_getBalance_hi(accountManager.unconfirmedBalance)}',
|
||||
style: theme.textTheme.headline4
|
||||
?.merge(_unconfirmedStyle())),
|
||||
Text(
|
||||
'${_getBalance_lo(accountManager.unconfirmedBalance)}',
|
||||
style: _unconfirmedStyle()),
|
||||
])
|
||||
: Container()),
|
||||
if (_progress > 0)
|
||||
LinearProgressIndicator(value: _progress / 100.0),
|
||||
]))),
|
||||
if (!simpleMode) NoteWidget(tabTo),
|
||||
HistoryWidget(tabTo),
|
||||
if (!simpleMode) BudgetWidget(),
|
||||
if (!simpleMode) PnLWidget(),
|
||||
ContactsTab(key: contactKey),
|
||||
]),
|
||||
floatingActionButton: _accountTab
|
||||
? FloatingActionButton(
|
||||
onPressed: _onSend,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
child: Icon(Icons.send),
|
||||
)
|
||||
: _contactsTab
|
||||
? FloatingActionButton(
|
||||
onPressed: _onAddContact,
|
||||
backgroundColor: theme.colorScheme.secondary,
|
||||
child: Icon(Icons.add),
|
||||
)
|
||||
: Container(), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
return Column(children: [
|
||||
if (simpleMode) Text(s.simpleMode),
|
||||
Observer(builder: (context) {
|
||||
final time = eta.eta;
|
||||
final syncedHeight = syncStatus.syncedHeight;
|
||||
final latestHeight = syncStatus.latestHeight;
|
||||
return syncedHeight == null
|
||||
? Text(s.rescanNeeded)
|
||||
: syncStatus.isSynced()
|
||||
? Text('$syncedHeight', style: theme.textTheme.caption)
|
||||
: Text('$syncedHeight / $latestHeight $time',
|
||||
style: theme.textTheme.caption!
|
||||
.apply(color: theme.primaryColor));
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
void tabTo(int index) {
|
||||
if (index != _tabController.index) _tabController.animateTo(index);
|
||||
}
|
||||
class QRAddressWidget extends StatefulWidget {
|
||||
@override
|
||||
QRAddressState createState() => QRAddressState();
|
||||
}
|
||||
|
||||
String _address() {
|
||||
final address = accountManager.showTAddr
|
||||
? accountManager.taddress
|
||||
: (_useSnapAddress
|
||||
? _uaAddress(_snapAddress, accountManager.taddress, settings.useUA)
|
||||
: _uaAddress(accountManager.active.address, accountManager.taddress,
|
||||
settings.useUA));
|
||||
return address;
|
||||
}
|
||||
class QRAddressState extends State<QRAddressWidget> {
|
||||
bool _useSnapAddress = false;
|
||||
String _snapAddress = "";
|
||||
|
||||
String _uaAddress(String zaddress, String taddress, bool useUA) =>
|
||||
useUA ? WarpApi.getUA(zaddress, taddress) : zaddress;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (context) {
|
||||
final s = S.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final simpleMode = settings.simpleMode;
|
||||
final address = _address();
|
||||
final shortAddress = addressLeftTrim(address);
|
||||
final showTAddr = active.showTAddr;
|
||||
final hasTAddr = active.taddress.isNotEmpty;
|
||||
final flat = settings.flat;
|
||||
final qrSize = getScreenSize(context) / 2.5;
|
||||
final hide = settings.autoHide && flat;
|
||||
final coinDef = active.coinDef;
|
||||
|
||||
_sign(int b) {
|
||||
return b < 0 ? '-' : '+';
|
||||
return Column(children: [
|
||||
if (hasTAddr)
|
||||
Text(showTAddr
|
||||
? s.tapQrCodeForShieldedAddress
|
||||
: s.tapQrCodeForTransparentAddress),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
GestureDetector(
|
||||
onTap: hasTAddr ? _onQRTap : null,
|
||||
child: RotatedBox(
|
||||
quarterTurns: hide ? 2 : 0,
|
||||
child: QrImage(
|
||||
data: address,
|
||||
size: qrSize,
|
||||
embeddedImage: coinDef.image,
|
||||
backgroundColor: Colors.white))),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
RichText(
|
||||
text: TextSpan(children: [
|
||||
TextSpan(text: '$shortAddress ', style: theme.textTheme.bodyText2),
|
||||
WidgetSpan(
|
||||
child: GestureDetector(
|
||||
child: Icon(Icons.content_copy), onTap: _onAddressCopy)),
|
||||
WidgetSpan(
|
||||
child: Padding(padding: EdgeInsets.symmetric(horizontal: 4))),
|
||||
WidgetSpan(
|
||||
child: GestureDetector(
|
||||
child: Icon(MdiIcons.qrcodeScan), onTap: _onReceive)),
|
||||
])),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
if (!simpleMode && !showTAddr)
|
||||
OutlinedButton(
|
||||
child: Text(s.newSnapAddress),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(width: 1, color: theme.primaryColor)),
|
||||
onPressed: _onSnapAddress),
|
||||
if (!simpleMode && showTAddr)
|
||||
OutlinedButton(
|
||||
child: Text(s.shieldTranspBalance),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(width: 1, color: theme.primaryColor)),
|
||||
onPressed: () {
|
||||
shieldTAddr(context);
|
||||
},
|
||||
)
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
_onQRTap() {
|
||||
accountManager.toggleShowTAddr();
|
||||
active.toggleShowTAddr();
|
||||
}
|
||||
|
||||
_onAddressCopy() {
|
||||
|
@ -384,62 +152,8 @@ class _AccountPageState extends State<AccountPage>
|
|||
Navigator.of(context).pushNamed('/receive', arguments: _address());
|
||||
}
|
||||
|
||||
_unconfirmedStyle() {
|
||||
return TextStyle(
|
||||
color: amountColor(context, accountManager.unconfirmedBalance));
|
||||
}
|
||||
|
||||
_getBalance_hi(int b) {
|
||||
return decimalFormat((b.abs() ~/ 100000) / 1000.0, 3);
|
||||
}
|
||||
|
||||
_getBalance_lo(b) {
|
||||
return (b.abs() % 100000).toString().padLeft(5, '0');
|
||||
}
|
||||
|
||||
_setupTimer() async {
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
await _trySync();
|
||||
_timerSync = Timer.periodic(Duration(seconds: 15), (Timer t) {
|
||||
_trySync();
|
||||
});
|
||||
}
|
||||
|
||||
double _fx() {
|
||||
return priceStore.zecPrice;
|
||||
}
|
||||
|
||||
_reorg() async {
|
||||
final targetHeight = syncStatus.syncedHeight - 10;
|
||||
WarpApi.rewindToHeight(targetHeight);
|
||||
syncStatus.setSyncHeight(targetHeight);
|
||||
await _trySync();
|
||||
}
|
||||
|
||||
_trySync() async {
|
||||
priceStore.fetchZecPrice();
|
||||
if (syncStatus.syncedHeight < 0) return;
|
||||
await syncStatus.update();
|
||||
await accountManager.updateUnconfirmedBalance();
|
||||
if (!syncStatus.isSynced()) {
|
||||
final res =
|
||||
await WarpApi.tryWarpSync(settings.getTx, settings.anchorOffset);
|
||||
if (res == 1) {
|
||||
await _reorg();
|
||||
} else if (res == 0) {
|
||||
syncStatus.update();
|
||||
}
|
||||
}
|
||||
await accountManager.fetchAccountData(false);
|
||||
await accountManager.updateBalance();
|
||||
await accountManager.updateTBalance();
|
||||
await accountManager.updateUnconfirmedBalance();
|
||||
await contacts.fetchContacts();
|
||||
accountManager.autoshield();
|
||||
}
|
||||
|
||||
_onSnapAddress() {
|
||||
final address = accountManager.newAddress();
|
||||
final address = active.newAddress();
|
||||
setState(() {
|
||||
_useSnapAddress = true;
|
||||
_snapAddress = address;
|
||||
|
@ -451,326 +165,123 @@ class _AccountPageState extends State<AccountPage>
|
|||
});
|
||||
}
|
||||
|
||||
_onSend() {
|
||||
Navigator.of(this.context).pushNamed('/send');
|
||||
String _address() {
|
||||
final address = active.showTAddr
|
||||
? active.taddress
|
||||
: _useSnapAddress
|
||||
? _snapAddress
|
||||
: active.account.address;
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
_onMenu(String choice) {
|
||||
switch (choice) {
|
||||
case "Accounts":
|
||||
Navigator.of(this.context).pushNamed('/accounts');
|
||||
break;
|
||||
case "Backup":
|
||||
_backup();
|
||||
break;
|
||||
case "Rescan":
|
||||
_rescan();
|
||||
break;
|
||||
case "Cold":
|
||||
_cold();
|
||||
break;
|
||||
case "MultiPay":
|
||||
_multiPay();
|
||||
break;
|
||||
case "Broadcast":
|
||||
_broadcast();
|
||||
break;
|
||||
case "Multisig":
|
||||
_multisig();
|
||||
break;
|
||||
case "Settings":
|
||||
_settings();
|
||||
break;
|
||||
case "Help":
|
||||
launch(DOC_URL);
|
||||
break;
|
||||
case "About":
|
||||
showAbout(this.context);
|
||||
break;
|
||||
case "Reorg":
|
||||
_reorg();
|
||||
break;
|
||||
case "Reset":
|
||||
_reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
class BalanceWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Observer(builder: (context) {
|
||||
final s = S.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final flat = settings.flat;
|
||||
final hide = settings.autoHide && flat;
|
||||
final showTAddr = active.showTAddr;
|
||||
final balance = showTAddr ? active.tbalance : active.balances.balance;
|
||||
final balanceColor = !showTAddr
|
||||
? theme.colorScheme.primaryVariant
|
||||
: theme.colorScheme.secondaryVariant;
|
||||
final balanceHi = hide ? '-------' : _getBalanceHi(balance);
|
||||
final deviceWidth = getWidth(context);
|
||||
final digits = deviceWidth.index < DeviceWidth.sm.index ? 7 : 9;
|
||||
final balanceStyle = (balanceHi.length > digits
|
||||
? theme.textTheme.headline4
|
||||
: theme.textTheme.headline2)!
|
||||
.copyWith(color: balanceColor);
|
||||
|
||||
_backup() async {
|
||||
final didAuthenticate = await authenticate(context, S.of(context).pleaseAuthenticateToShowAccountSeed);
|
||||
if (didAuthenticate) {
|
||||
Navigator.of(context).pushNamed('/backup');
|
||||
}
|
||||
}
|
||||
final fx = priceStore.coinPrice;
|
||||
final balanceFX = balance * fx / ZECUNIT;
|
||||
final coinDef = active.coinDef;
|
||||
|
||||
_rescan() async {
|
||||
final approved = await rescanDialog(context);
|
||||
if (approved) {
|
||||
syncStatus.sync(context);
|
||||
}
|
||||
}
|
||||
return Column(children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: <Widget>[
|
||||
if (!hide)
|
||||
Text('${coinDef.symbol}', style: theme.textTheme.headline5),
|
||||
Text(' $balanceHi', style: balanceStyle),
|
||||
if (!hide) Text('${_getBalanceLo(balance)}'),
|
||||
]),
|
||||
if (hide) Text(s.tiltYourDeviceUpToRevealYourBalance),
|
||||
if (!hide && fx != 0.0)
|
||||
Text("${decimalFormat(balanceFX, 2, symbol: settings.currency)}",
|
||||
style: theme.textTheme.headline6),
|
||||
if (!hide && fx != 0.0)
|
||||
Text(
|
||||
"1 ${coinDef.ticker} = ${decimalFormat(
|
||||
fx, 2, symbol: settings.currency)}"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
_cold() {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(S.of(context).coldStorage),
|
||||
content:
|
||||
Text(S.of(context).doYouWantToDeleteTheSecretKeyAndConvert),
|
||||
actions: confirmButtons(context, _convertToWatchOnly,
|
||||
okLabel: S.of(context).delete)));
|
||||
}
|
||||
class MemPoolWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Observer(builder: (context) {
|
||||
final b = active.balances;
|
||||
final theme = Theme.of(context);
|
||||
final unconfirmedBalance = b.unconfirmedBalance;
|
||||
if (unconfirmedBalance == 0) return Container();
|
||||
final unconfirmedStyle = TextStyle(
|
||||
color: amountColor(context, unconfirmedBalance));
|
||||
|
||||
_multiPay() {
|
||||
Navigator.of(context).pushNamed('/multipay');
|
||||
}
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'${_sign(unconfirmedBalance)} ${_getBalanceHi(unconfirmedBalance)}',
|
||||
style: theme.textTheme.headline4
|
||||
?.merge(unconfirmedStyle)),
|
||||
Text(
|
||||
'${_getBalanceLo(unconfirmedBalance)}',
|
||||
style: unconfirmedStyle),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
_broadcast() async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
class ProgressWidget extends StatefulWidget {
|
||||
@override
|
||||
ProgressState createState() => ProgressState();
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
final res = WarpApi.broadcast(result.files.single.path!);
|
||||
final snackBar = SnackBar(content: Text(res));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
class ProgressState extends State<ProgressWidget> {
|
||||
int _progress = 0;
|
||||
StreamSubscription? _progressDispose;
|
||||
|
||||
_multisig() {
|
||||
Navigator.of(context).pushNamed('/multisig');
|
||||
}
|
||||
|
||||
_convertToWatchOnly() {
|
||||
accountManager.convertToWatchOnly();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
_settings() {
|
||||
Navigator.of(context).pushNamed('/settings');
|
||||
}
|
||||
|
||||
_reset() {
|
||||
Navigator.of(context).pushNamed('/reset');
|
||||
}
|
||||
|
||||
_onAddContact() async {
|
||||
final contact = await contactKey.currentState
|
||||
?.showContactForm(context, Contact.empty());
|
||||
if (contact != null) {
|
||||
contacts.add(contact);
|
||||
}
|
||||
}
|
||||
|
||||
_handleAccel(AccelerometerEvent event) {
|
||||
final n = sqrt(event.x * event.x + event.y * event.y + event.z * event.z);
|
||||
final inclination = acos(event.z / n) / pi * 180 * event.y.sign;
|
||||
final flat = inclination < 20;
|
||||
if (flat != _flat)
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_progressDispose = progressStream.listen((percent) {
|
||||
setState(() {
|
||||
_flat = flat;
|
||||
_progress = percent;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class BudgetWidget extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => BudgetState();
|
||||
}
|
||||
|
||||
class BudgetState extends State<BudgetWidget>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true; //Set to true
|
||||
void dispose() {
|
||||
_progressDispose?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Observer(builder: (context) {
|
||||
final _ = accountManager.dataEpoch;
|
||||
return Column(
|
||||
children: [
|
||||
Card(
|
||||
child: Column(children: [
|
||||
Text(S.of(context).largestSpendingsByAddress,
|
||||
style: Theme.of(context).textTheme.headline6),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
BudgetChart(),
|
||||
])),
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: Column(children: [
|
||||
Text(S.of(context).accountBalanceHistory,
|
||||
style: Theme.of(context).textTheme.headline6),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
Expanded(child: Padding(padding: EdgeInsets.only(right: 20),
|
||||
child: LineChartTimeSeries.fromTimeSeries(accountManager.accountBalances)))
|
||||
]))),
|
||||
],
|
||||
);
|
||||
}));
|
||||
if (_progress == 0) return Container();
|
||||
return LinearProgressIndicator(value: _progress / 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
class PnLWidget extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => PnLState();
|
||||
}
|
||||
_getBalanceHi(int b) => decimalFormat((b.abs() ~/ 100000) / 1000.0, 3);
|
||||
|
||||
class PnLState extends State<PnLWidget> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true; //Set to true
|
||||
_getBalanceLo(int b) => (b.abs() % 100000).toString().padLeft(5, '0');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconTheme.merge(
|
||||
data: IconThemeData(opacity: 0.54),
|
||||
child:
|
||||
Column(children: [
|
||||
Row(children: [
|
||||
Expanded(child:
|
||||
FormBuilderRadioGroup(
|
||||
orientation: OptionsOrientation.horizontal,
|
||||
name: S.of(context).pnl,
|
||||
initialValue: accountManager.pnlSeriesIndex,
|
||||
onChanged: (int? v) {
|
||||
setState(() {
|
||||
accountManager.setPnlSeriesIndex(v!);
|
||||
});
|
||||
},
|
||||
options: [
|
||||
FormBuilderFieldOption(child: Text(S.of(context).price), value: 0),
|
||||
FormBuilderFieldOption(
|
||||
child: Text(S.of(context).realized), value: 1),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).mm), value: 2),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).total), value: 3),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).qty), value: 4),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).table), value: 5),
|
||||
])),
|
||||
IconButton(onPressed: _onExport, icon: Icon(Icons.save)),
|
||||
]),
|
||||
Observer(builder: (context) {
|
||||
final _ = accountManager.pnlSorted;
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 20),
|
||||
child: accountManager.pnlSeriesIndex != 5
|
||||
? PnLChart(
|
||||
accountManager.pnls, accountManager.pnlSeriesIndex)
|
||||
: PnLTable()));
|
||||
})
|
||||
]));
|
||||
}
|
||||
|
||||
_onExport() async {
|
||||
final csvData = accountManager.pnlSorted.map((pnl) => [
|
||||
pnl.timestamp,
|
||||
pnl.amount,
|
||||
pnl.price,
|
||||
pnl.realized,
|
||||
pnl.unrealized,
|
||||
pnl.realized + pnl.unrealized]).toList();
|
||||
await shareCsv(csvData, 'pnl_history.csv', S.of(context).pnlHistory);
|
||||
}
|
||||
}
|
||||
|
||||
class PnLChart extends StatelessWidget {
|
||||
final List<PnL> pnls;
|
||||
final int seriesIndex;
|
||||
|
||||
PnLChart(this.pnls, this.seriesIndex);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final series = _createSeries(pnls, seriesIndex, context);
|
||||
return LineChartTimeSeries.fromTimeSeries(series);
|
||||
}
|
||||
|
||||
static double _seriesData(PnL pnl, int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return pnl.price;
|
||||
case 1:
|
||||
return pnl.realized;
|
||||
case 2:
|
||||
return pnl.unrealized;
|
||||
case 3:
|
||||
return pnl.realized + pnl.unrealized;
|
||||
case 4:
|
||||
return pnl.amount;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static List<TimeSeriesPoint<double>> _createSeries(
|
||||
List<PnL> data, int index, BuildContext context) {
|
||||
return data
|
||||
.map((pnl) => TimeSeriesPoint(
|
||||
pnl.timestamp.millisecondsSinceEpoch ~/ DAY_MS,
|
||||
_seriesData(pnl, index)))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class PnLTable extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sortSymbol = accountManager.pnlDesc ? ' \u2193' : ' \u2191';
|
||||
return SingleChildScrollView(
|
||||
child: Observer(
|
||||
builder: (context) => PaginatedDataTable(
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text(S.of(context).date + sortSymbol),
|
||||
onSort: (_, __) {
|
||||
accountManager.togglePnlDesc();
|
||||
}),
|
||||
DataColumn(label: Text(S.of(context).qty), numeric: true),
|
||||
DataColumn(label: Text(S.of(context).price), numeric: true),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).realized), numeric: true),
|
||||
DataColumn(label: Text(S.of(context).mm), numeric: true),
|
||||
DataColumn(label: Text(S.of(context).total), numeric: true),
|
||||
],
|
||||
columnSpacing: 16,
|
||||
showCheckboxColumn: false,
|
||||
availableRowsPerPage: [5, 10, 25, 100],
|
||||
onRowsPerPageChanged: (int? value) {
|
||||
settings.setRowsPerPage(value ?? 25);
|
||||
},
|
||||
showFirstLastButtons: true,
|
||||
rowsPerPage: settings.rowsPerPage,
|
||||
source: PnLDataSource(context))));
|
||||
}
|
||||
}
|
||||
|
||||
class PnLDataSource extends DataTableSource {
|
||||
BuildContext context;
|
||||
final dateFormat = DateFormat("MM-dd");
|
||||
|
||||
PnLDataSource(this.context);
|
||||
|
||||
@override
|
||||
DataRow getRow(int index) {
|
||||
final pnl = accountManager.pnlSorted[index];
|
||||
final ts = dateFormat.format(pnl.timestamp);
|
||||
return DataRow(cells: [
|
||||
DataCell(Text("$ts")),
|
||||
DataCell(Text(decimalFormat(pnl.amount, 2))),
|
||||
DataCell(Text(decimalFormat(pnl.price, 3))),
|
||||
DataCell(Text(decimalFormat(pnl.realized, 3))),
|
||||
DataCell(Text(decimalFormat(pnl.unrealized, 3))),
|
||||
DataCell(Text(decimalFormat(pnl.realized + pnl.unrealized, 3))),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get rowCount => accountManager.pnls.length;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => 0;
|
||||
}
|
||||
_sign(int b) => b < 0 ? '-' : '+';
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import 'package:ZYWallet/accounts.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:warp/main.dart';
|
||||
import 'package:warp/store.dart';
|
||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'backup.dart';
|
||||
import 'main.dart';
|
||||
import 'store.dart';
|
||||
import 'generated/l10n.dart';
|
||||
|
||||
import 'about.dart';
|
||||
|
||||
class AccountManagerPage extends StatefulWidget {
|
||||
|
@ -14,23 +17,20 @@ class AccountManagerPage extends StatefulWidget {
|
|||
|
||||
class AccountManagerState extends State<AccountManagerPage> {
|
||||
var _accountNameController = TextEditingController();
|
||||
var _tbalances = Map<int, int>();
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
Future.microtask(() async {
|
||||
await accountManager.refresh();
|
||||
final tbalances = await accountManager.getAllTBalances();
|
||||
setState(() {
|
||||
_tbalances = tbalances;
|
||||
});
|
||||
await accounts.refresh();
|
||||
await accounts.updateTBalance();
|
||||
});
|
||||
showAboutOnce(this.context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(S.of(context).selectAccount), actions: [
|
||||
PopupMenuButton<String>(
|
||||
|
@ -43,43 +43,73 @@ class AccountManagerState extends State<AccountManagerPage> {
|
|||
onSelected: _onMenu)
|
||||
]),
|
||||
body: Padding(padding: EdgeInsets.all(8), child: Observer(
|
||||
builder: (context) =>
|
||||
accountManager.accounts.isEmpty
|
||||
? Center(child: NoAccount())
|
||||
: ListView.builder(
|
||||
itemCount: accountManager.accounts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final a = accountManager.accounts[index];
|
||||
final zbal = a.balance / ZECUNIT;
|
||||
final tbal = (_tbalances[a.id] ?? 0) / ZECUNIT;
|
||||
final balance = zbal + tbal;
|
||||
return Card(
|
||||
child: Dismissible(
|
||||
key: Key(a.name),
|
||||
child: ListTile(
|
||||
title: Text(a.name,
|
||||
style: Theme.of(context).textTheme.headline5),
|
||||
subtitle: Text("${decimalFormat(zbal, 3)} + ${_tbalances[a.id] != null ? decimalFormat(tbal, 3) : '?'}"),
|
||||
trailing: Text(decimalFormat(balance, 3)),
|
||||
onTap: () {
|
||||
_selectAccount(a);
|
||||
},
|
||||
onLongPress: () {
|
||||
_editAccount(a);
|
||||
},
|
||||
),
|
||||
confirmDismiss: (d) => _onAccountDelete(a),
|
||||
onDismissed: (d) =>
|
||||
_onDismissed(index, a),
|
||||
));
|
||||
}),
|
||||
builder: (context) {
|
||||
final _1 = accounts.epoch;
|
||||
return accounts.list.isEmpty
|
||||
? Center(child: NoAccount())
|
||||
: ListView.builder(
|
||||
itemCount: accounts.list.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final a = accounts.list[index];
|
||||
final weight = settings.coins[a.coin].active == a.id ? FontWeight.bold : FontWeight.normal;
|
||||
final zbal = a.balance / ZECUNIT;
|
||||
final tbal = a.tbalance / ZECUNIT;
|
||||
final balance = zbal + tbal;
|
||||
return Card(
|
||||
child: Dismissible(
|
||||
key: Key(a.name),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(backgroundImage: settings.coins[a.coin].def.image),
|
||||
title: Text(a.name,
|
||||
style: theme.textTheme.headline5
|
||||
?.merge(TextStyle(fontWeight: weight))
|
||||
.apply(color: a.coin == 0 ? theme.colorScheme.primary : theme.colorScheme.secondary,
|
||||
)),
|
||||
subtitle: Text("${decimalFormat(zbal, 3)} + ${decimalFormat(tbal, 3)}"),
|
||||
trailing: Text(decimalFormat(balance, 3)),
|
||||
onTap: () {
|
||||
_selectAccount(a);
|
||||
},
|
||||
onLongPress: () {
|
||||
_editAccount(a);
|
||||
},
|
||||
),
|
||||
confirmDismiss: (d) => _onAccountDelete(a),
|
||||
onDismissed: (d) =>
|
||||
_onDismissed(index, a),
|
||||
));
|
||||
});},
|
||||
)),
|
||||
floatingActionButton: GestureDetector(onLongPress: _onFullRestore, child: FloatingActionButton(
|
||||
onPressed: _onRestore, child: Icon(Icons.add))));
|
||||
floatingActionButton: SpeedDial(
|
||||
icon: Icons.add,
|
||||
onPress: _onRestore,
|
||||
children: [
|
||||
SpeedDialChild(
|
||||
child: Icon(Icons.download),
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
label: 'Restore Batch',
|
||||
onTap: _onFullRestore,
|
||||
),
|
||||
SpeedDialChild(
|
||||
child: Icon(Icons.upload),
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
label: 'Save Batch',
|
||||
onTap: _onFullBackup,
|
||||
),
|
||||
SpeedDialChild(
|
||||
child: Icon(Icons.subdirectory_arrow_right),
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
label: 'New Sub-account',
|
||||
onTap: _onNewSubaccount,
|
||||
),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
Future<bool> _onAccountDelete(Account account) async {
|
||||
if (accountManager.accounts.length == 1) return false;
|
||||
final confirm1 = await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
|
@ -94,7 +124,7 @@ class AccountManagerState extends State<AccountManagerPage> {
|
|||
if (!confirm2) return false;
|
||||
|
||||
final zbal = account.balance;
|
||||
final tbal = _tbalances[account.id] ?? 0;
|
||||
final tbal = account.tbalance;
|
||||
if (zbal + tbal > 0) {
|
||||
final confirm3 = await showDialog<bool>(
|
||||
context: context,
|
||||
|
@ -112,27 +142,21 @@ class AccountManagerState extends State<AccountManagerPage> {
|
|||
}
|
||||
|
||||
void _onDismissed(int index, Account account) async {
|
||||
await accountManager.delete(account.id);
|
||||
accountManager.refresh();
|
||||
await accounts.delete(account.coin, account.id);
|
||||
accounts.refresh();
|
||||
}
|
||||
|
||||
_selectAccount(Account account) async {
|
||||
await accountManager.setActiveAccount(account);
|
||||
await active.setActiveAccount(account.coin, account.id);
|
||||
if (syncStatus.accountRestored) {
|
||||
syncStatus.setAccountRestored(false);
|
||||
final approved = await rescanDialog(context);
|
||||
if (approved)
|
||||
syncStatus.sync(context);
|
||||
}
|
||||
else if (syncStatus.syncedHeight < 0) {
|
||||
syncStatus.setSyncedToLatestHeight();
|
||||
syncStatus.rescan(context);
|
||||
}
|
||||
|
||||
final navigator = Navigator.of(context);
|
||||
if (navigator.canPop())
|
||||
navigator.pop();
|
||||
else
|
||||
navigator.pushReplacementNamed('/account');
|
||||
navigator.pushNamedAndRemoveUntil('/account', (route) => false);
|
||||
}
|
||||
|
||||
_editAccount(Account account) async {
|
||||
|
@ -146,7 +170,7 @@ class AccountManagerState extends State<AccountManagerPage> {
|
|||
}
|
||||
|
||||
_changeAccountName(Account account) {
|
||||
accountManager.changeAccountName(account, _accountNameController.text);
|
||||
accounts.changeAccountName(account.coin, account.id, _accountNameController.text);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
|
@ -169,6 +193,32 @@ class AccountManagerState extends State<AccountManagerPage> {
|
|||
Navigator.of(this.context).pushNamed('/settings');
|
||||
}
|
||||
|
||||
_onNewSubaccount() async {
|
||||
final s = S.of(context);
|
||||
if (active.id == 0) {
|
||||
showSnackBar(s.noActiveAccount);
|
||||
return;
|
||||
}
|
||||
final newName = s.subAccountOf(active.account.name);
|
||||
_accountNameController.text = newName;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(s.newSubAccount),
|
||||
content: TextField(controller: _accountNameController),
|
||||
actions: confirmButtons(context, () {
|
||||
Navigator.of(context).pop(true);
|
||||
})));
|
||||
if (confirmed == true) {
|
||||
WarpApi.newSubAccount(active.coin, active.id, _accountNameController.text);
|
||||
await accounts.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
_onFullBackup() {
|
||||
Navigator.of(context).pushNamed('/fullBackup');
|
||||
}
|
||||
|
||||
_onFullRestore() {
|
||||
Navigator.of(this.context).pushNamed('/fullRestore');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,345 @@
|
|||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'coin/coins.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'db.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
|
||||
import 'backup.dart';
|
||||
import 'coin/coin.dart';
|
||||
import 'coin/zcash.dart';
|
||||
import 'main.dart';
|
||||
import 'store.dart';
|
||||
|
||||
part 'accounts.g.dart';
|
||||
|
||||
class Account {
|
||||
final int coin;
|
||||
final int id;
|
||||
final String name;
|
||||
final String address;
|
||||
final int balance;
|
||||
int tbalance = 0;
|
||||
final ShareInfo? share;
|
||||
|
||||
Account(this.coin, this.id, this.name, this.address, this.balance, this.tbalance, this.share);
|
||||
}
|
||||
|
||||
final Account emptyAccount = Account(0, 0, "", "", 0, 0, null);
|
||||
|
||||
class AccountManager2 = _AccountManager2 with _$AccountManager2;
|
||||
|
||||
abstract class _AccountManager2 with Store {
|
||||
@observable int epoch = 0;
|
||||
List<Account> list = [];
|
||||
|
||||
@action
|
||||
Future<void> refresh() async {
|
||||
List<Account> _list = [];
|
||||
_list.addAll(await _getList(0));
|
||||
_list.addAll(await _getList(1));
|
||||
list = _list;
|
||||
epoch += 1;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> updateTBalance() async {
|
||||
for (var a in list) {
|
||||
final tbalance = await WarpApi.getTBalanceAsync(a.coin, a.id);
|
||||
a.tbalance = tbalance;
|
||||
}
|
||||
epoch += 1;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> delete(int coin, int id) async {
|
||||
WarpApi.deleteAccount(coin, id);
|
||||
if (active.coin == coin && active.id == id)
|
||||
active.reset();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> changeAccountName(int coin, int id, String name) async {
|
||||
final c = settings.coins[coin].def; // TODO: Do in backend would be cleaner
|
||||
final db = c.db;
|
||||
await db.execute("UPDATE accounts SET name = ?2 WHERE id_account = ?1",
|
||||
[id, name]);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
void saveActive(int coin, int id) {
|
||||
settings.coins[coin].active = id;
|
||||
Future.microtask(() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final def = settings.coins[coin].def;
|
||||
prefs.setInt("${def.ticker}.active", id);
|
||||
});
|
||||
}
|
||||
|
||||
Account get(int coin, int id) => list.firstWhere((e) => e.coin == coin && e.id == id, orElse: () => emptyAccount);
|
||||
|
||||
static Future<List<Account>> _getList(int coin) async {
|
||||
final c = settings.coins[coin].def;
|
||||
final db = c.db;
|
||||
List<Account> accounts = [];
|
||||
|
||||
final List<Map> res = await db.rawQuery(
|
||||
"WITH notes AS (SELECT a.id_account, a.name, a.address, CASE WHEN r.spent IS NULL THEN r.value ELSE 0 END AS nv FROM accounts a LEFT JOIN received_notes r ON a.id_account = r.account),"
|
||||
"accounts2 AS (SELECT id_account, name, address, COALESCE(sum(nv), 0) AS balance FROM notes GROUP by id_account) "
|
||||
"SELECT a.id_account, a.name, a.address, a.balance FROM accounts2 a",
|
||||
[]);
|
||||
for (var r in res) {
|
||||
final int id = r['id_account'];
|
||||
// final shareInfo = r['secret'] != null
|
||||
// ? ShareInfo(
|
||||
// r['idx'], r['threshold'], r['participants'], r['secret'])
|
||||
// : null; // TODO: Multisig
|
||||
final account = Account(coin,
|
||||
r['id_account'], r['name'], r['address'], r['balance'], 0, null);
|
||||
accounts.add(account);
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
}
|
||||
|
||||
class ActiveAccount = _ActiveAccount with _$ActiveAccount;
|
||||
|
||||
abstract class _ActiveAccount with Store {
|
||||
@observable
|
||||
int dataEpoch = 0;
|
||||
|
||||
int coin = 0;
|
||||
int id = 0;
|
||||
|
||||
Account account = emptyAccount;
|
||||
CoinBase coinDef = ZcashCoin();
|
||||
bool canPay = false;
|
||||
@observable Balances balances = Balances.zero;
|
||||
String taddress = "";
|
||||
int tbalance = 0;
|
||||
|
||||
@observable List<Note> notes = [];
|
||||
@observable List<Tx> txs = [];
|
||||
@observable List<Spending> spendings = [];
|
||||
@observable List<TimeSeriesPoint<double>> accountBalances = [];
|
||||
@observable List<PnL> pnls = [];
|
||||
|
||||
@observable
|
||||
bool showTAddr = false;
|
||||
|
||||
@observable
|
||||
SortConfig noteSortConfig = SortConfig("", SortOrder.Unsorted);
|
||||
|
||||
@observable
|
||||
SortConfig txSortConfig = SortConfig("", SortOrder.Unsorted);
|
||||
|
||||
@observable
|
||||
int pnlSeriesIndex = 0;
|
||||
|
||||
@observable
|
||||
bool pnlDesc = false;
|
||||
|
||||
@action
|
||||
Future<void> restore() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
coin = prefs.getInt('coin') ?? 0;
|
||||
id = prefs.getInt('account') ?? 0;
|
||||
setActiveAccount(coin, id);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
setActiveAccount(0, 0);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> setActiveAccount(int _coin, int _id) async {
|
||||
coin = _coin;
|
||||
id = _id;
|
||||
accounts.saveActive(coin, id);
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setInt('coin', coin);
|
||||
prefs.setInt('account', id);
|
||||
|
||||
coinDef = settings.coins[coin].def;
|
||||
final db = coinDef.db;
|
||||
|
||||
account = accounts.get(coin, id);
|
||||
|
||||
if (id > 0) {
|
||||
final List<Map> res1 = await db.rawQuery(
|
||||
"SELECT address FROM taddrs WHERE account = ?1", [id]);
|
||||
taddress = res1.isNotEmpty ? res1[0]['address'] : "";
|
||||
|
||||
final List<Map> res2 = await db.rawQuery(
|
||||
"SELECT sk FROM accounts WHERE id_account = ?1", [id]);
|
||||
canPay = res2.isNotEmpty && res2[0]['sk'] != null;
|
||||
}
|
||||
|
||||
showTAddr = false;
|
||||
balances = Balances.zero;
|
||||
WarpApi.setMempoolAccount(coin, id);
|
||||
|
||||
await update();
|
||||
await priceStore.updateChart();
|
||||
}
|
||||
|
||||
@action
|
||||
void toggleShowTAddr() {
|
||||
showTAddr = !showTAddr;
|
||||
}
|
||||
|
||||
@action
|
||||
void updateTBalance() {
|
||||
tbalance = WarpApi.getTBalance(coin, id);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> updateBalances() async {
|
||||
final dbr = DbReader(coin, id);
|
||||
balances = await dbr.getBalance(syncStatus.confirmHeight);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> update() async {
|
||||
await updateBalances();
|
||||
updateTBalance();
|
||||
final dbr = DbReader(coin, id);
|
||||
notes = await dbr.getNotes();
|
||||
txs = await dbr.getTxs();
|
||||
dataEpoch += 1;
|
||||
}
|
||||
|
||||
String newAddress() {
|
||||
return WarpApi.newAddress(coin, id);
|
||||
}
|
||||
|
||||
@computed
|
||||
List<Note> get sortedNotes {
|
||||
var notes2 = [...notes];
|
||||
switch (noteSortConfig.field) {
|
||||
case "time":
|
||||
return _sort(notes2, (Note note) => note.height, noteSortConfig.order);
|
||||
case "amount":
|
||||
return _sort(notes2, (Note note) => note.value, noteSortConfig.order);
|
||||
}
|
||||
return notes2;
|
||||
}
|
||||
|
||||
@computed
|
||||
List<Tx> get sortedTxs {
|
||||
var txs2 = [...txs];
|
||||
switch (txSortConfig.field) {
|
||||
case "time":
|
||||
return _sort(txs2, (Tx tx) => tx.height, txSortConfig.order);
|
||||
case "amount":
|
||||
return _sort(txs2, (Tx tx) => tx.value, txSortConfig.order);
|
||||
case "txid":
|
||||
return _sort(txs2, (Tx tx) => tx.txid, txSortConfig.order);
|
||||
case "address":
|
||||
return _sort(
|
||||
txs2, (Tx tx) => tx.contact ?? tx.address, txSortConfig.order);
|
||||
case "memo":
|
||||
return _sort(txs2, (Tx tx) => tx.memo, txSortConfig.order);
|
||||
}
|
||||
return txs2;
|
||||
}
|
||||
|
||||
@action
|
||||
void sortNotes(String field) {
|
||||
noteSortConfig = noteSortConfig.sortOn(field);
|
||||
}
|
||||
|
||||
@action
|
||||
void sortTx(String field) {
|
||||
txSortConfig = txSortConfig.sortOn(field);
|
||||
}
|
||||
|
||||
List<C> _sort<C extends HasHeight, T extends Comparable>(
|
||||
List<C> items, T Function(C) project, SortOrder order) {
|
||||
switch (order) {
|
||||
case SortOrder.Ascending:
|
||||
items.sort((a, b) => project(a).compareTo(project(b)));
|
||||
break;
|
||||
case SortOrder.Descending:
|
||||
items.sort((a, b) => -project(a).compareTo(project(b)));
|
||||
break;
|
||||
case SortOrder.Unsorted:
|
||||
items.sort((a, b) => -a.height.compareTo(b.height));
|
||||
break;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@action
|
||||
void setPnlSeriesIndex(int index) {
|
||||
pnlSeriesIndex = index;
|
||||
}
|
||||
|
||||
@computed
|
||||
List<PnL> get pnlSorted {
|
||||
if (pnlDesc) {
|
||||
var _pnls = [...pnls.reversed];
|
||||
return _pnls;
|
||||
}
|
||||
return pnls;
|
||||
}
|
||||
|
||||
@action
|
||||
void togglePnlDesc() {
|
||||
pnlDesc = !pnlDesc;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> excludeNote(Note note) async {
|
||||
await coinDef.db.execute(
|
||||
"UPDATE received_notes SET excluded = ?2 WHERE id_note = ?1",
|
||||
[note.id, note.excluded]);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> invertExcludedNotes() async {
|
||||
await coinDef.db.execute(
|
||||
"UPDATE received_notes SET excluded = NOT(COALESCE(excluded, 0)) WHERE account = ?1",
|
||||
[active.id]);
|
||||
notes = notes.map((n) => n.invertExcluded).toList();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> fetchChartData() async {
|
||||
final dbr = DbReader(active.coin, active.id);
|
||||
pnls = await dbr.getPNL(active.id);
|
||||
spendings = await dbr.getSpending(active.id);
|
||||
accountBalances = await dbr.getAccountBalanceTimeSeries(active.id, active.balances.balance);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> convertToWatchOnly() async {
|
||||
await coinDef.db.rawUpdate(
|
||||
"UPDATE accounts SET seed = NULL, sk = NULL WHERE id_account = ?1",
|
||||
[active.id]);
|
||||
canPay = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Backup> getBackup(AccountId account) async {
|
||||
final c = settings.coins[account.coin].def;
|
||||
final db = c.db;
|
||||
final List<Map> res = await db.rawQuery(
|
||||
"SELECT name, seed, aindex, sk, ivk FROM accounts WHERE id_account = ?1",
|
||||
[account.id]);
|
||||
if (res.isEmpty) throw Exception("Account N/A");
|
||||
// final share = await getShareInfo(account); // Multisig
|
||||
final row = res[0];
|
||||
final name = row['name'];
|
||||
final seed = row['seed'];
|
||||
final index = row['aindex'];
|
||||
final sk = row['sk'];
|
||||
final ivk = row['ivk'];
|
||||
int type = 0;
|
||||
if (seed != null)
|
||||
type = 0;
|
||||
else if (sk != null)
|
||||
type = 1;
|
||||
else if (ivk != null) type = 2;
|
||||
return Backup(type, name, seed, index, sk, ivk, null);
|
||||
}
|
|
@ -3,9 +3,16 @@ import 'package:flutter/material.dart';
|
|||
import 'main.dart';
|
||||
import 'store.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'accounts.dart' show getBackup;
|
||||
|
||||
class AccountId {
|
||||
final int coin;
|
||||
final int id;
|
||||
AccountId(this.coin, this.id);
|
||||
}
|
||||
|
||||
class BackupPage extends StatefulWidget {
|
||||
final int? accountId;
|
||||
final AccountId? accountId;
|
||||
|
||||
BackupPage(this.accountId);
|
||||
|
||||
|
@ -21,8 +28,8 @@ class BackupState extends State<BackupPage> {
|
|||
final _shareController = TextEditingController();
|
||||
|
||||
Future<bool> _init() async {
|
||||
backup = await accountManager.getBackup(widget.accountId ?? accountManager.active.id);
|
||||
_backupController.text = backup.value();
|
||||
backup = await getBackup(widget.accountId ?? AccountId(active.coin, active.id));
|
||||
_backupController.text = backupData;
|
||||
_skController.text = backup.sk ?? "";
|
||||
_ivkController.text = backup.ivk;
|
||||
final share = backup.share;
|
||||
|
@ -30,6 +37,8 @@ class BackupState extends State<BackupPage> {
|
|||
return true;
|
||||
}
|
||||
|
||||
String get backupData => backup.value() + (backup.index != 0 ? " (Index: ${backup.index})" : "");
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(appBar: AppBar(title: Text(S.of(context).backup)), body:
|
||||
|
@ -51,8 +60,8 @@ class BackupState extends State<BackupPage> {
|
|||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).backupDataRequiredForRestore, prefixIcon: IconButton(icon: Icon(Icons.save),
|
||||
onPressed: () => _showQR(backup.value(), "$name - backup"))),
|
||||
labelText: s.backupDataRequiredForRestore(name), prefixIcon: IconButton(icon: Icon(Icons.save),
|
||||
onPressed: () => _showQR(backupData, "$name - backup"))),
|
||||
controller: _backupController,
|
||||
minLines: 3,
|
||||
maxLines: 10,
|
||||
|
@ -88,17 +97,12 @@ class BackupState extends State<BackupPage> {
|
|||
),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
Text(s.tapAnIconToShowTheQrCode),
|
||||
Container(margin: EdgeInsets.all(8), padding: EdgeInsets.all(8), decoration: BoxDecoration(border: Border.all(width: 2, color: theme.primaryColor), borderRadius: BorderRadius.circular(4)),child:
|
||||
GestureDetector(onLongPress: _onFullBackup, child: Text(s.backupWarning,
|
||||
style: theme.textTheme.subtitle1!.copyWith(color: theme.primaryColor)))),
|
||||
Container(margin: EdgeInsets.all(8), padding: EdgeInsets.all(8), decoration: BoxDecoration(border: Border.all(width: 2, color: theme.primaryColor), borderRadius: BorderRadius.circular(4)),
|
||||
child: Text(s.backupWarning, style: theme.textTheme.subtitle1!.copyWith(color: theme.primaryColor))),
|
||||
]
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
_showQR(String text, String title) => showQR(context, text, title);
|
||||
|
||||
_onFullBackup() {
|
||||
Navigator.of(context).pushNamed('/fullBackup');
|
||||
}
|
||||
}
|
||||
|
|
217
lib/budget.dart
217
lib/budget.dart
|
@ -3,27 +3,234 @@ import 'dart:ui';
|
|||
|
||||
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:flutter_palette/flutter_palette.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
|
||||
import 'chart.dart';
|
||||
import 'store.dart';
|
||||
import 'main.dart';
|
||||
import 'generated/l10n.dart';
|
||||
|
||||
class BudgetChart extends StatefulWidget {
|
||||
class BudgetWidget extends StatefulWidget {
|
||||
@override
|
||||
BudgetState createState() => BudgetState();
|
||||
State<StatefulWidget> createState() => BudgetState();
|
||||
}
|
||||
|
||||
class BudgetState extends State<BudgetChart> {
|
||||
class BudgetState extends State<BudgetWidget>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true; //Set to true
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Observer(builder: (context) {
|
||||
final _ = active.dataEpoch;
|
||||
return Column(
|
||||
children: [
|
||||
Card(
|
||||
child: Column(children: [
|
||||
Text(S.of(context).largestSpendingsByAddress,
|
||||
style: Theme.of(context).textTheme.headline6),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
BudgetChart(),
|
||||
])),
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: Column(children: [
|
||||
Text(S.of(context).accountBalanceHistory,
|
||||
style: Theme.of(context).textTheme.headline6),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
||||
Expanded(child: Padding(padding: EdgeInsets.only(right: 20),
|
||||
child: LineChartTimeSeries.fromTimeSeries(active.accountBalances)))
|
||||
]))),
|
||||
],
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class PnLWidget extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => PnLState();
|
||||
}
|
||||
|
||||
class PnLState extends State<PnLWidget> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true; //Set to true
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconTheme.merge(
|
||||
data: IconThemeData(opacity: 0.54),
|
||||
child:
|
||||
Column(children: [
|
||||
Row(children: [
|
||||
Expanded(child:
|
||||
FormBuilderRadioGroup(
|
||||
orientation: OptionsOrientation.horizontal,
|
||||
name: S.of(context).pnl,
|
||||
initialValue: active.pnlSeriesIndex,
|
||||
onChanged: (int? v) {
|
||||
setState(() {
|
||||
active.setPnlSeriesIndex(v!);
|
||||
});
|
||||
},
|
||||
options: [
|
||||
FormBuilderFieldOption(child: Text(S.of(context).price), value: 0),
|
||||
FormBuilderFieldOption(
|
||||
child: Text(S.of(context).realized), value: 1),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).mm), value: 2),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).total), value: 3),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).qty), value: 4),
|
||||
FormBuilderFieldOption(child: Text(S.of(context).table), value: 5),
|
||||
])),
|
||||
IconButton(onPressed: _onExport, icon: Icon(Icons.save)),
|
||||
]),
|
||||
Observer(builder: (context) {
|
||||
final _ = active.pnlSorted;
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 20),
|
||||
child: active.pnlSeriesIndex != 5
|
||||
? PnLChart(
|
||||
active.pnls, active.pnlSeriesIndex)
|
||||
: PnLTable()));
|
||||
})
|
||||
]));
|
||||
}
|
||||
|
||||
_onExport() async {
|
||||
final csvData = active.pnlSorted.map((pnl) => [
|
||||
pnl.timestamp,
|
||||
pnl.amount,
|
||||
pnl.price,
|
||||
pnl.realized,
|
||||
pnl.unrealized,
|
||||
pnl.realized + pnl.unrealized]).toList();
|
||||
await shareCsv(csvData, 'pnl_history.csv', S.of(context).pnlHistory);
|
||||
}
|
||||
}
|
||||
|
||||
class PnLChart extends StatelessWidget {
|
||||
final List<PnL> pnls;
|
||||
final int seriesIndex;
|
||||
|
||||
PnLChart(this.pnls, this.seriesIndex);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final series = _createSeries(pnls, seriesIndex, context);
|
||||
return LineChartTimeSeries.fromTimeSeries(series);
|
||||
}
|
||||
|
||||
static double _seriesData(PnL pnl, int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return pnl.price;
|
||||
case 1:
|
||||
return pnl.realized;
|
||||
case 2:
|
||||
return pnl.unrealized;
|
||||
case 3:
|
||||
return pnl.realized + pnl.unrealized;
|
||||
case 4:
|
||||
return pnl.amount;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static List<TimeSeriesPoint<double>> _createSeries(
|
||||
List<PnL> data, int index, BuildContext context) {
|
||||
return data
|
||||
.map((pnl) => TimeSeriesPoint(
|
||||
pnl.timestamp.millisecondsSinceEpoch ~/ DAY_MS,
|
||||
_seriesData(pnl, index)))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class PnLTable extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sortSymbol = active.pnlDesc ? ' \u2193' : ' \u2191';
|
||||
return SingleChildScrollView(
|
||||
child: Observer(
|
||||
builder: (context) => PaginatedDataTable(
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text(S.of(context).date + sortSymbol),
|
||||
onSort: (_, __) {
|
||||
active.togglePnlDesc();
|
||||
}),
|
||||
DataColumn(label: Text(S.of(context).qty), numeric: true),
|
||||
DataColumn(label: Text(S.of(context).price), numeric: true),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).realized), numeric: true),
|
||||
DataColumn(label: Text(S.of(context).mm), numeric: true),
|
||||
DataColumn(label: Text(S.of(context).total), numeric: true),
|
||||
],
|
||||
columnSpacing: 16,
|
||||
showCheckboxColumn: false,
|
||||
availableRowsPerPage: [5, 10, 25, 100],
|
||||
onRowsPerPageChanged: (int? value) {
|
||||
settings.setRowsPerPage(value ?? 25);
|
||||
},
|
||||
showFirstLastButtons: true,
|
||||
rowsPerPage: settings.rowsPerPage,
|
||||
source: PnLDataSource(context))));
|
||||
}
|
||||
}
|
||||
|
||||
class PnLDataSource extends DataTableSource {
|
||||
BuildContext context;
|
||||
final dateFormat = DateFormat("MM-dd");
|
||||
|
||||
PnLDataSource(this.context);
|
||||
|
||||
@override
|
||||
DataRow getRow(int index) {
|
||||
final pnl = active.pnlSorted[index];
|
||||
final ts = dateFormat.format(pnl.timestamp);
|
||||
return DataRow(cells: [
|
||||
DataCell(Text("$ts")),
|
||||
DataCell(Text(decimalFormat(pnl.amount, 2))),
|
||||
DataCell(Text(decimalFormat(pnl.price, 3))),
|
||||
DataCell(Text(decimalFormat(pnl.realized, 3))),
|
||||
DataCell(Text(decimalFormat(pnl.unrealized, 3))),
|
||||
DataCell(Text(decimalFormat(pnl.realized + pnl.unrealized, 3))),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get rowCount => active.pnls.length;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => 0;
|
||||
}
|
||||
|
||||
class BudgetChart extends StatefulWidget {
|
||||
@override
|
||||
BudgetChartState createState() => BudgetChartState();
|
||||
}
|
||||
|
||||
class BudgetChartState extends State<BudgetChart> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) => Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
BudgetBar(accountManager.spendings),
|
||||
BudgetTable(accountManager.spendings)
|
||||
BudgetBar(active.spendings),
|
||||
BudgetTable(active.spendings)
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,30 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
class LWInstance {
|
||||
String name;
|
||||
String url;
|
||||
|
||||
LWInstance(this.name, this.url);
|
||||
}
|
||||
|
||||
abstract class CoinBase {
|
||||
String get app;
|
||||
String get symbol;
|
||||
String get currency;
|
||||
String get ticker;
|
||||
String get explorerUrl;
|
||||
AssetImage get image;
|
||||
String get dbName;
|
||||
late Database db;
|
||||
List<LWInstance> get lwd;
|
||||
bool get supportsUA;
|
||||
bool get supportsMultisig;
|
||||
List<double> get weights;
|
||||
|
||||
Future<void> open(String dbPath) async {
|
||||
final path = join(dbPath, dbName);
|
||||
db = await openDatabase(path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import 'coin.dart';
|
||||
import 'ycash.dart';
|
||||
import 'zcash.dart';
|
||||
|
||||
CoinBase ycash = YcashCoin();
|
||||
CoinBase zcash = ZcashCoin();
|
||||
|
|
@ -1,15 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import "coin.dart";
|
||||
|
||||
class Coin {
|
||||
class YcashCoin extends CoinBase {
|
||||
String app = "YWallet";
|
||||
String symbol = "\u24E8";
|
||||
String currency = "ycash";
|
||||
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"),
|
||||
];
|
||||
bool supportsUA = false;
|
||||
bool supportsMultisig = true;
|
||||
List<int> weights = [5, 25, 250];
|
||||
List<double> weights = [5, 25, 250];
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'coin.dart';
|
||||
|
||||
class Coin {
|
||||
class ZcashCoin extends CoinBase {
|
||||
String app = "ZWallet";
|
||||
String symbol = "\u24E9";
|
||||
String currency = "zcash";
|
||||
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"),
|
||||
LWInstance("Zecwallet", "https://lwdv3.zecwallet.co"),
|
||||
|
|
|
@ -6,11 +6,11 @@ import 'package:warp_api/warp_api.dart';
|
|||
|
||||
import 'main.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'settings.dart';
|
||||
import 'store.dart';
|
||||
|
||||
class ContactsTab extends StatefulWidget {
|
||||
ContactsTab({Key? key}) : super(key: key);
|
||||
|
||||
ContactsTab({key: Key}): super(key: key);
|
||||
@override
|
||||
State<StatefulWidget> createState() => ContactsState();
|
||||
}
|
||||
|
@ -22,32 +22,32 @@ class ContactsState extends State<ContactsTab> {
|
|||
Padding(padding: EdgeInsets.all(8), child: contacts.contacts.isEmpty
|
||||
? NoContact()
|
||||
: Column(children: [
|
||||
if (contacts.dirty) OutlinedButton(onPressed: accountManager.canPay ? _onCommit : null, child: Text(S.of(context).saveToBlockchain), style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1, color: Theme.of(context).primaryColor))),
|
||||
Expanded(child: ListView.builder(
|
||||
itemCount: contacts.contacts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final c = contacts.contacts[index];
|
||||
return Card(
|
||||
child: Dismissible(
|
||||
key: Key("${c.id}"),
|
||||
child: ListTile(
|
||||
title: Text(c.name,
|
||||
style: Theme.of(context).textTheme.headline5),
|
||||
subtitle: Text(c.address),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () { _onContact(c); },
|
||||
onLongPress: () { _editContact(c); },
|
||||
),
|
||||
confirmDismiss: (_) async {
|
||||
return await _onConfirmDelContact(c);
|
||||
},
|
||||
onDismissed: (_) {
|
||||
_delContact(c);
|
||||
}));
|
||||
})
|
||||
)]))
|
||||
if (!settings.coins[active.coin].contactsSaved) OutlinedButton(onPressed: active.canPay ? _onCommit : null, child: Text(S.of(context).saveToBlockchain), style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1, color: Theme.of(context).primaryColor))),
|
||||
Expanded(child: ListView.builder(
|
||||
itemCount: contacts.contacts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final c = contacts.contacts[index];
|
||||
return Card(
|
||||
child: Dismissible(
|
||||
key: Key("${c.id}"),
|
||||
child: ListTile(
|
||||
title: Text(c.name,
|
||||
style: Theme.of(context).textTheme.headline5),
|
||||
subtitle: Text(c.address),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () { _onContact(c); },
|
||||
onLongPress: () { _editContact(c); },
|
||||
),
|
||||
confirmDismiss: (_) async {
|
||||
return await _onConfirmDelContact(c);
|
||||
},
|
||||
onDismissed: (_) {
|
||||
_delContact(c);
|
||||
}));
|
||||
})
|
||||
)]))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ class ContactsState extends State<ContactsTab> {
|
|||
|
||||
_editContact(Contact c) async {
|
||||
final contact = await showContactForm(context, c);
|
||||
if (contact != null) contacts.add(contact);
|
||||
contacts.add(contact);
|
||||
}
|
||||
|
||||
Future<bool> _onConfirmDelContact(Contact c) async {
|
||||
|
@ -89,14 +89,14 @@ class ContactsState extends State<ContactsTab> {
|
|||
|
||||
_onCommit() async {
|
||||
final approve = await showMessageBox(context, S.of(context).saveToBlockchain,
|
||||
S.of(context).areYouSureYouWantToSaveYourContactsIt(coin.ticker),
|
||||
S.of(context).areYouSureYouWantToSaveYourContactsIt(activeCoin().ticker),
|
||||
S.of(context).ok);
|
||||
if (approve) {
|
||||
contacts.markContactsDirty(false);
|
||||
final tx = await WarpApi.commitUnsavedContacts(
|
||||
accountManager.active.id, settings.anchorOffset);
|
||||
active.coin, active.id, settings.anchorOffset);
|
||||
final snackBar = SnackBar(content: Text("${S.of(context).txId}: $tx"));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
||||
contacts.markContactsSaved(active.coin, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ class ContactState extends State<ContactForm> {
|
|||
state.save();
|
||||
final contact = Contact(widget.contact.id, nameController.text, address);
|
||||
Navigator.of(context).pop(contact);
|
||||
accountManager.fetchAccountData(true);
|
||||
active.update();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +214,7 @@ class AddressState extends State<AddressInput> {
|
|||
if (v == null || v.isEmpty) return S.of(context).addressIsEmpty;
|
||||
final zaddr = WarpApi.getSaplingFromUA(v);
|
||||
if (zaddr.isNotEmpty) return null;
|
||||
if (!WarpApi.validAddress(v)) return S.of(context).invalidAddress;
|
||||
if (!WarpApi.validAddress(active.coin, v)) return S.of(context).invalidAddress;
|
||||
if (contacts.contacts.where((c) => c.address == v).isNotEmpty) return S.of(context).duplicateContact;
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'store.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'coin/coins.dart';
|
||||
import 'main.dart';
|
||||
|
||||
final DateFormat noteDateFormat = DateFormat("yy-MM-dd HH:mm");
|
||||
final DateFormat txDateFormat = DateFormat("MM-dd HH:mm");
|
||||
|
||||
class DbReader {
|
||||
int coin;
|
||||
int id;
|
||||
Database db;
|
||||
|
||||
DbReader(int coin, int id): this.init(coin, id, settings.coins[coin].def.db);
|
||||
DbReader.init(this.coin, this.id, this.db);
|
||||
|
||||
Future<Balances> getBalance(int confirmHeight) async {
|
||||
final balance = Sqflite.firstIntValue(await db.rawQuery(
|
||||
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND (spent IS NULL OR spent = 0)",
|
||||
[id])) ?? 0;
|
||||
final shieldedBalance = Sqflite.firstIntValue(await db.rawQuery(
|
||||
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL",
|
||||
[id])) ?? 0;
|
||||
final unconfirmedSpentBalance = Sqflite.firstIntValue(await db.rawQuery(
|
||||
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent = 0",
|
||||
[id])) ?? 0;
|
||||
final underConfirmedBalance = Sqflite.firstIntValue(await db.rawQuery(
|
||||
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND height > ?2",
|
||||
[id, confirmHeight])) ?? 0;
|
||||
final excludedBalance = Sqflite.firstIntValue(await db.rawQuery(
|
||||
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL "
|
||||
"AND height <= ?2 AND excluded",
|
||||
[id, confirmHeight])) ?? 0;
|
||||
final unconfirmedBalance = await WarpApi.mempoolSync(coin);
|
||||
|
||||
return Balances(balance, shieldedBalance, unconfirmedSpentBalance, underConfirmedBalance, excludedBalance, unconfirmedBalance);
|
||||
}
|
||||
|
||||
Future<List<Note>> getNotes() async {
|
||||
final List<Map> res = await db.rawQuery(
|
||||
"SELECT n.id_note, n.height, n.value, t.timestamp, n.excluded, n.spent FROM received_notes n, transactions t "
|
||||
"WHERE n.account = ?1 AND (n.spent IS NULL OR n.spent = 0) "
|
||||
"AND n.tx = t.id_tx ORDER BY n.height DESC",
|
||||
[id]);
|
||||
final notes = res.map((row) {
|
||||
final id = row['id_note'];
|
||||
final height = row['height'];
|
||||
final timestamp = noteDateFormat
|
||||
.format(DateTime.fromMillisecondsSinceEpoch(row['timestamp'] * 1000));
|
||||
final excluded = (row['excluded'] ?? 0) != 0;
|
||||
final spent = row['spent'] == 0;
|
||||
return Note(
|
||||
id, height, timestamp, row['value'] / ZECUNIT, excluded, spent);
|
||||
}).toList();
|
||||
print("NOTES ${notes.length}");
|
||||
return notes;
|
||||
}
|
||||
|
||||
Future<List<Tx>> getTxs() async {
|
||||
final List<Map> res2 = await db.rawQuery(
|
||||
"SELECT id_tx, txid, height, timestamp, t.address, c.name, value, memo FROM transactions t "
|
||||
"LEFT JOIN contacts c ON t.address = c.address WHERE account = ?1 ORDER BY height DESC",
|
||||
[id]);
|
||||
final txs = res2.map((row) {
|
||||
Uint8List txid = row['txid'];
|
||||
final fullTxId = hex.encode(txid.reversed.toList());
|
||||
final shortTxid = fullTxId.substring(0, 8);
|
||||
final timestamp = txDateFormat
|
||||
.format(DateTime.fromMillisecondsSinceEpoch(row['timestamp'] * 1000));
|
||||
return Tx(
|
||||
row['id_tx'],
|
||||
row['height'],
|
||||
timestamp,
|
||||
shortTxid,
|
||||
fullTxId,
|
||||
row['value'] / ZECUNIT,
|
||||
row['address'] ?? "",
|
||||
row['name'],
|
||||
row['memo'] ?? "");
|
||||
}).toList();
|
||||
print("TXS ${txs.length}");
|
||||
return txs;
|
||||
}
|
||||
|
||||
Future<List<PnL>> getPNL(int accountId) async {
|
||||
final range = _getChartRange();
|
||||
|
||||
final List<Map> res1 = await db.rawQuery(
|
||||
"SELECT timestamp, value FROM transactions WHERE timestamp >= ?2 AND account = ?1",
|
||||
[accountId, range.start ~/ 1000]);
|
||||
final List<Trade> trades = [];
|
||||
for (var row in res1) {
|
||||
final dt = DateTime.fromMillisecondsSinceEpoch(row['timestamp'] * 1000);
|
||||
final qty = row['value'] / ZECUNIT;
|
||||
trades.add(Trade(dt, qty));
|
||||
}
|
||||
|
||||
final portfolioTimeSeries = sampleDaily<Trade, Trade, double>(
|
||||
trades,
|
||||
range.start,
|
||||
range.end,
|
||||
(t) => t.dt.millisecondsSinceEpoch ~/ DAY_MS,
|
||||
(t) => t,
|
||||
(acc, t) => acc + t.qty,
|
||||
0.0);
|
||||
|
||||
final List<Map> res2 = await db.rawQuery(
|
||||
"SELECT timestamp, price FROM historical_prices WHERE timestamp >= ?2 AND currency = ?1",
|
||||
[settings.currency, range.start ~/ 1000]);
|
||||
final List<Quote> quotes = [];
|
||||
for (var row in res2) {
|
||||
final dt = DateTime.fromMillisecondsSinceEpoch(row['timestamp'] * 1000);
|
||||
final price = row['price'];
|
||||
quotes.add(Quote(dt, price));
|
||||
}
|
||||
|
||||
var prevBalance = 0.0;
|
||||
var cash = 0.0;
|
||||
var realized = 0.0;
|
||||
final List<PnL> pnls = [];
|
||||
final len = min(quotes.length, portfolioTimeSeries.length);
|
||||
for (var i = 0; i < len; i++) {
|
||||
final dt = quotes[i].dt;
|
||||
final price = quotes[i].price;
|
||||
final balance = portfolioTimeSeries[i].value;
|
||||
final qty = balance - prevBalance;
|
||||
|
||||
final closeQty = qty * balance < 0
|
||||
? min(qty.abs(), prevBalance.abs()) * qty.sign
|
||||
: 0.0;
|
||||
final openQty = qty - closeQty;
|
||||
final avgPrice = prevBalance != 0 ? cash / prevBalance : 0.0;
|
||||
|
||||
cash += openQty * price + closeQty * avgPrice;
|
||||
realized += closeQty * (avgPrice - price);
|
||||
final unrealized = price * balance - cash;
|
||||
|
||||
final pnl = PnL(dt, price, balance, realized, unrealized);
|
||||
pnls.add(pnl);
|
||||
|
||||
prevBalance = balance;
|
||||
}
|
||||
return pnls;
|
||||
}
|
||||
|
||||
Future<List<Spending>> getSpending(int accountId) async {
|
||||
final range = _getChartRange();
|
||||
final List<Map> res = await db.rawQuery(
|
||||
"SELECT SUM(value) as v, t.address, c.name FROM transactions t LEFT JOIN contacts c ON t.address = c.address "
|
||||
"WHERE account = ?1 AND timestamp >= ?2 AND value < 0 GROUP BY t.address ORDER BY v ASC LIMIT 5",
|
||||
[accountId, range.start ~/ 1000]);
|
||||
final spendings = res.map((row) {
|
||||
final address = row['address'] ?? "";
|
||||
final value = -row['v'] / ZECUNIT;
|
||||
final contact = row['name'];
|
||||
return Spending(address, value, contact);
|
||||
}).toList();
|
||||
return spendings;
|
||||
}
|
||||
|
||||
Future<List<TimeSeriesPoint<double>>> getAccountBalanceTimeSeries(int accountId, int balance) async {
|
||||
final range = _getChartRange();
|
||||
final List<Map> res = await db.rawQuery(
|
||||
"SELECT timestamp, value FROM transactions WHERE account = ?1 AND timestamp >= ?2 ORDER BY timestamp DESC",
|
||||
[accountId, range.start ~/ 1000]);
|
||||
List<AccountBalance> _accountBalances = [];
|
||||
var b = balance;
|
||||
_accountBalances.add(AccountBalance(DateTime.now(), b / ZECUNIT));
|
||||
for (var row in res) {
|
||||
final timestamp =
|
||||
DateTime.fromMillisecondsSinceEpoch(row['timestamp'] * 1000);
|
||||
final value = row['value'] as int;
|
||||
final ab = AccountBalance(timestamp, b / ZECUNIT);
|
||||
_accountBalances.add(ab);
|
||||
b -= value;
|
||||
}
|
||||
_accountBalances.add(AccountBalance(
|
||||
DateTime.fromMillisecondsSinceEpoch(range.start), b / ZECUNIT));
|
||||
_accountBalances = _accountBalances.reversed.toList();
|
||||
final accountBalances = sampleDaily<AccountBalance, double, double>(
|
||||
_accountBalances,
|
||||
range.start,
|
||||
range.end,
|
||||
(AccountBalance ab) => ab.time.millisecondsSinceEpoch ~/ DAY_MS,
|
||||
(AccountBalance ab) => ab.balance,
|
||||
(acc, v) => v,
|
||||
0.0);
|
||||
return accountBalances;
|
||||
}
|
||||
|
||||
TimeRange _getChartRange() {
|
||||
final now = DateTime.now().toUtc();
|
||||
final today = DateTime.utc(now.year, now.month, now.day);
|
||||
final start = today.add(Duration(days: -_chartRangeDays()));
|
||||
final cutoff = start.millisecondsSinceEpoch;
|
||||
return TimeRange(cutoff, today.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
int _chartRangeDays() {
|
||||
switch (settings.chartRange) {
|
||||
case '1M':
|
||||
return 30;
|
||||
case '3M':
|
||||
return 90;
|
||||
case '6M':
|
||||
return 180;
|
||||
}
|
||||
return 365;
|
||||
}
|
||||
}
|
||||
|
||||
class Balances {
|
||||
final int balance;
|
||||
final int shieldedBalance;
|
||||
final int unconfirmedSpentBalance;
|
||||
final int underConfirmedBalance;
|
||||
final int excludedBalance;
|
||||
final int unconfirmedBalance;
|
||||
|
||||
Balances(this.balance, this.shieldedBalance, this.unconfirmedSpentBalance, this.underConfirmedBalance, this.excludedBalance, this.unconfirmedBalance);
|
||||
static Balances zero = Balances(0, 0, 0, 0, 0, 0);
|
||||
}
|
|
@ -17,8 +17,8 @@ class DualMoneyInputWidget extends StatefulWidget {
|
|||
class DualMoneyInputState extends State<DualMoneyInputWidget> {
|
||||
static final zero = decimalFormat(0, 3);
|
||||
var useMillis = true;
|
||||
var inputInZEC = true;
|
||||
var zecAmountController = TextEditingController(text: zero);
|
||||
var inputInCoin = true;
|
||||
var coinAmountController = TextEditingController(text: zero);
|
||||
var fiatAmountController = TextEditingController(text: zero);
|
||||
var amount = 0;
|
||||
|
||||
|
@ -46,17 +46,17 @@ class DualMoneyInputState extends State<DualMoneyInputWidget> {
|
|||
Row(children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
style: !inputInZEC
|
||||
style: !inputInCoin
|
||||
? TextStyle(fontWeight: FontWeight.w200)
|
||||
: TextStyle(),
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
s.amountInSettingscurrency(coin.ticker)),
|
||||
controller: zecAmountController,
|
||||
s.amountInSettingscurrency(active.coinDef.ticker)),
|
||||
controller: coinAmountController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [makeInputFormatter(useMillis)],
|
||||
onTap: () => setState(() {
|
||||
inputInZEC = true;
|
||||
inputInCoin = true;
|
||||
}),
|
||||
validator: _checkAmount,
|
||||
onChanged: (_) => _updateFiatAmount(),
|
||||
|
@ -67,7 +67,7 @@ class DualMoneyInputState extends State<DualMoneyInputWidget> {
|
|||
Row(children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
style: inputInZEC
|
||||
style: inputInCoin
|
||||
? TextStyle(fontWeight: FontWeight.w200)
|
||||
: TextStyle(),
|
||||
decoration: InputDecoration(
|
||||
|
@ -78,7 +78,7 @@ class DualMoneyInputState extends State<DualMoneyInputWidget> {
|
|||
inputFormatters: [makeInputFormatter(useMillis)],
|
||||
validator: (v) => _checkAmount(v, isFiat: true),
|
||||
onTap: () => setState(() {
|
||||
inputInZEC = false;
|
||||
inputInCoin = false;
|
||||
}),
|
||||
onChanged: (_) => _updateAmount()))
|
||||
]),
|
||||
|
@ -88,7 +88,7 @@ class DualMoneyInputState extends State<DualMoneyInputWidget> {
|
|||
void clear() {
|
||||
setState(() {
|
||||
useMillis = true;
|
||||
zecAmountController.text = zero;
|
||||
coinAmountController.text = zero;
|
||||
fiatAmountController.text = zero;
|
||||
});
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ class DualMoneyInputState extends State<DualMoneyInputWidget> {
|
|||
void setAmount(int amount) {
|
||||
setState(() {
|
||||
useMillis = false;
|
||||
zecAmountController.text = amountToString(amount);
|
||||
coinAmountController.text = amountToString(amount);
|
||||
_updateFiatAmount();
|
||||
});
|
||||
}
|
||||
|
@ -123,17 +123,17 @@ class DualMoneyInputState extends State<DualMoneyInputWidget> {
|
|||
}
|
||||
|
||||
void _updateAmount() {
|
||||
final rate = 1.0 / priceStore.zecPrice;
|
||||
final rate = 1.0 / priceStore.coinPrice;
|
||||
final amount = parseNumber(fiatAmountController.text);
|
||||
final otherAmount = decimalFormat(amount * rate, precision(useMillis));
|
||||
setState(() {
|
||||
zecAmountController.text = otherAmount;
|
||||
coinAmountController.text = otherAmount;
|
||||
});
|
||||
}
|
||||
|
||||
void _updateFiatAmount() {
|
||||
final rate = priceStore.zecPrice;
|
||||
final amount = parseNumber(zecAmountController.text);
|
||||
final rate = priceStore.coinPrice;
|
||||
final amount = parseNumber(coinAmountController.text);
|
||||
final otherAmount = decimalFormat(amount * rate, precision(useMillis));
|
||||
setState(() {
|
||||
fiatAmountController.text = otherAmount;
|
||||
|
|
|
@ -25,33 +25,37 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
static String m1(ticker) =>
|
||||
"Are you sure you want to save your contacts? It will cost 0.01 m${ticker}";
|
||||
|
||||
static String m2(address, amount) =>
|
||||
static String m2(name) => "Backup Data - ${name} - Required for Restore";
|
||||
|
||||
static String m3(address, amount) =>
|
||||
"Do you want to sign a transaction to ${address} for ${amount}";
|
||||
|
||||
static String m3(ticker) =>
|
||||
static String m4(ticker) =>
|
||||
"Do you want to transfer your entire transparent balance to your shielded address? A Network fee of 0.01 m${ticker} will be deducted.";
|
||||
|
||||
static String m4(app) => "${app} Encrypted Backup";
|
||||
static String m5(app) => "${app} Encrypted Backup";
|
||||
|
||||
static String m5(num) => "${num} more signers needed";
|
||||
static String m6(num) => "${num} more signers needed";
|
||||
|
||||
static String m6(ticker) => "Receive ${ticker}";
|
||||
static String m7(ticker) => "Receive ${ticker}";
|
||||
|
||||
static String m7(ticker) => "Send ${ticker}";
|
||||
static String m8(ticker) => "Send ${ticker}";
|
||||
|
||||
static String m8(ticker) => "Send ${ticker} to...";
|
||||
static String m9(ticker) => "Send ${ticker} to...";
|
||||
|
||||
static String m9(app) => "Sent from ${app}";
|
||||
static String m10(app) => "Sent from ${app}";
|
||||
|
||||
static String m10(amount, ticker, count) =>
|
||||
static String m11(amount, ticker, count) =>
|
||||
"Sending a total of ${amount} ${ticker} to ${count} recipients";
|
||||
|
||||
static String m11(aZEC, ticker, address) =>
|
||||
static String m12(aZEC, ticker, address) =>
|
||||
"Sending ${aZEC} ${ticker} to ${address}";
|
||||
|
||||
static String m12(text) => "${text} copied to clipboard";
|
||||
static String m13(name) => "Sub Account of ${name}";
|
||||
|
||||
static String m13(currency) => "Use ${currency}";
|
||||
static String m14(text) => "${text} copied to clipboard";
|
||||
|
||||
static String m15(currency) => "Use ${currency}";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
|
@ -67,6 +71,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"accountHasSomeBalanceAreYouSureYouWantTo":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Account has some BALANCE. Are you sure you want to delete it?"),
|
||||
"accountIndex": MessageLookupByLibrary.simpleMessage("Account Index"),
|
||||
"accountName": MessageLookupByLibrary.simpleMessage("Account Name"),
|
||||
"accountNameIsRequired":
|
||||
MessageLookupByLibrary.simpleMessage("Account name is required"),
|
||||
|
@ -104,8 +109,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"backup": MessageLookupByLibrary.simpleMessage("Backup"),
|
||||
"backupAllAccounts":
|
||||
MessageLookupByLibrary.simpleMessage("Backup All Accounts"),
|
||||
"backupDataRequiredForRestore": MessageLookupByLibrary.simpleMessage(
|
||||
"Backup Data - Required for Restore"),
|
||||
"backupDataRequiredForRestore": m2,
|
||||
"backupEncryptionKey":
|
||||
MessageLookupByLibrary.simpleMessage("Backup Encryption Key"),
|
||||
"backupWarning": MessageLookupByLibrary.simpleMessage(
|
||||
|
@ -127,7 +131,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"Are you SURE you want to DELETE this account? You MUST have a BACKUP to recover it. This operation is NOT reversible."),
|
||||
"confirmResetApp": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to reset the app? Your accounts will NOT be deleted"),
|
||||
"confirmSignATransactionToAddressFor": m2,
|
||||
"confirmSignATransactionToAddressFor": m3,
|
||||
"confirmSigning":
|
||||
MessageLookupByLibrary.simpleMessage("Confirm Signing"),
|
||||
"confs": MessageLookupByLibrary.simpleMessage("Confs"),
|
||||
|
@ -150,12 +154,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"doYouWantToDeleteTheSecretKeyAndConvert":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Do you want to DELETE the secret key and convert this account to a watch-only account? You will not be able to spend from this device anymore. This operation is NOT reversible."),
|
||||
"doYouWantToTransferYourEntireTransparentBalanceTo": m3,
|
||||
"doYouWantToTransferYourEntireTransparentBalanceTo": m4,
|
||||
"duplicateAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Duplicate Account"),
|
||||
"duplicateContact": MessageLookupByLibrary.simpleMessage(
|
||||
"Another contact has this address"),
|
||||
"encryptedBackup": m4,
|
||||
"encryptedBackup": m5,
|
||||
"enterSecretShareIfAccountIsMultisignature":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Enter secret share if account is multi-signature"),
|
||||
|
@ -199,7 +203,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"nameIsEmpty": MessageLookupByLibrary.simpleMessage("Name is empty"),
|
||||
"newSnapAddress":
|
||||
MessageLookupByLibrary.simpleMessage("New Snap Address"),
|
||||
"newSubAccount":
|
||||
MessageLookupByLibrary.simpleMessage("New Sub Account"),
|
||||
"noAccount": MessageLookupByLibrary.simpleMessage("No account"),
|
||||
"noActiveAccount":
|
||||
MessageLookupByLibrary.simpleMessage("No active account"),
|
||||
"noAuthenticationMethod":
|
||||
MessageLookupByLibrary.simpleMessage("No Authentication Method"),
|
||||
"noContacts": MessageLookupByLibrary.simpleMessage("No Contacts"),
|
||||
|
@ -209,7 +217,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"notEnoughBalance":
|
||||
MessageLookupByLibrary.simpleMessage("Not enough balance"),
|
||||
"notes": MessageLookupByLibrary.simpleMessage("Notes"),
|
||||
"numMoreSignersNeeded": m5,
|
||||
"numMoreSignersNeeded": m6,
|
||||
"numberOfConfirmationsNeededBeforeSpending":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Number of Confirmations Needed before Spending"),
|
||||
|
@ -238,7 +246,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"purple": MessageLookupByLibrary.simpleMessage("Purple"),
|
||||
"qty": MessageLookupByLibrary.simpleMessage("Qty"),
|
||||
"realized": MessageLookupByLibrary.simpleMessage("Realized"),
|
||||
"receive": m6,
|
||||
"receive": m7,
|
||||
"receivePayment":
|
||||
MessageLookupByLibrary.simpleMessage("Receive Payment"),
|
||||
"rescan": MessageLookupByLibrary.simpleMessage("Rescan"),
|
||||
|
@ -267,11 +275,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage(
|
||||
"Select notes to EXCLUDE from payments"),
|
||||
"send": MessageLookupByLibrary.simpleMessage("Send"),
|
||||
"sendCointicker": m7,
|
||||
"sendCointickerTo": m8,
|
||||
"sendFrom": m9,
|
||||
"sendingATotalOfAmountCointickerToCountRecipients": m10,
|
||||
"sendingAzecCointickerToAddress": m11,
|
||||
"sendCointicker": m8,
|
||||
"sendCointickerTo": m9,
|
||||
"sendFrom": m10,
|
||||
"sendingATotalOfAmountCointickerToCountRecipients": m11,
|
||||
"sendingAzecCointickerToAddress": m12,
|
||||
"server": MessageLookupByLibrary.simpleMessage("Server"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
|
||||
"shieldTranspBalance":
|
||||
|
@ -285,10 +293,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage("Shielding in progress..."),
|
||||
"sign": MessageLookupByLibrary.simpleMessage("Sign Transaction"),
|
||||
"simple": MessageLookupByLibrary.simpleMessage("Simple"),
|
||||
"simpleMode": MessageLookupByLibrary.simpleMessage("Simple Mode"),
|
||||
"spendable": MessageLookupByLibrary.simpleMessage("Spendable"),
|
||||
"spendableBalance":
|
||||
MessageLookupByLibrary.simpleMessage("Spendable Balance"),
|
||||
"splitAccount": MessageLookupByLibrary.simpleMessage("Split Account"),
|
||||
"subAccountOf": m13,
|
||||
"synching": MessageLookupByLibrary.simpleMessage("Synching"),
|
||||
"table": MessageLookupByLibrary.simpleMessage("Table"),
|
||||
"tapAnIconToShowTheQrCode": MessageLookupByLibrary.simpleMessage(
|
||||
|
@ -302,7 +312,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"Tap QR Code for Transparent Address"),
|
||||
"tapTransactionForDetails":
|
||||
MessageLookupByLibrary.simpleMessage("Tap Transaction for Details"),
|
||||
"textCopiedToClipboard": m12,
|
||||
"textCopiedToClipboard": m14,
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
|
||||
"themeEditor": MessageLookupByLibrary.simpleMessage("Theme Editor"),
|
||||
"thisAccountAlreadyExists": MessageLookupByLibrary.simpleMessage(
|
||||
|
@ -331,7 +341,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage("Unshielded Balance"),
|
||||
"unsignedTransactionFile":
|
||||
MessageLookupByLibrary.simpleMessage("Unsigned Transaction File"),
|
||||
"useSettingscurrency": m13,
|
||||
"useSettingscurrency": m15,
|
||||
"useTransparentBalance":
|
||||
MessageLookupByLibrary.simpleMessage("Use Transparent Balance"),
|
||||
"useUa": MessageLookupByLibrary.simpleMessage("Use UA"),
|
||||
|
|
|
@ -25,31 +25,36 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
static String m1(ticker) =>
|
||||
"Estás seguro de que quieres guardar tus contactos? Costará 0,01 mZEC ";
|
||||
|
||||
static String m2(address, amount) =>
|
||||
static String m2(name) =>
|
||||
"Copia De Seguridad - ${name} - Requerido Para Restaurar";
|
||||
|
||||
static String m3(address, amount) =>
|
||||
"Do you want to sign a transaction to ${address} for ${amount}";
|
||||
|
||||
static String m3(ticker) =>
|
||||
static String m4(ticker) =>
|
||||
"¿Quiere transferir su saldo transparente a su dirección blindada? ";
|
||||
|
||||
static String m4(app) => "${app} Encrypted Backup";
|
||||
static String m5(app) => "${app} Encrypted Backup";
|
||||
|
||||
static String m6(ticker) => "Recibir ${ticker}";
|
||||
static String m7(ticker) => "Recibir ${ticker}";
|
||||
|
||||
static String m7(ticker) => "Enviar ${ticker}";
|
||||
static String m8(ticker) => "Enviar ${ticker}";
|
||||
|
||||
static String m8(ticker) => "Enviar ${ticker} a…";
|
||||
static String m9(ticker) => "Enviar ${ticker} a…";
|
||||
|
||||
static String m9(app) => "Enviado desde ${app}";
|
||||
static String m10(app) => "Enviado desde ${app}";
|
||||
|
||||
static String m10(amount, ticker, count) =>
|
||||
static String m11(amount, ticker, count) =>
|
||||
"Enviando un total de ${amount} ${ticker} a ${count} direcciones";
|
||||
|
||||
static String m11(aZEC, ticker, address) =>
|
||||
static String m12(aZEC, ticker, address) =>
|
||||
"Enviado ${aZEC} ${ticker} a ${address}";
|
||||
|
||||
static String m12(text) => "${text} copied to clipboard";
|
||||
static String m13(name) => "Sub Account of ${name}";
|
||||
|
||||
static String m13(currency) => "Utilizar ${currency}";
|
||||
static String m14(text) => "${text} copied to clipboard";
|
||||
|
||||
static String m15(currency) => "Utilizar ${currency}";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
|
@ -65,6 +70,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"accountHasSomeBalanceAreYouSureYouWantTo":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"La cuenta tiene un saldo. ¿Estás seguro de que quieres eliminarlo?"),
|
||||
"accountIndex": MessageLookupByLibrary.simpleMessage("Account Index"),
|
||||
"accountName": MessageLookupByLibrary.simpleMessage("Nombre de Cuenta"),
|
||||
"accountNameIsRequired": MessageLookupByLibrary.simpleMessage(
|
||||
"Se requiere el nombre de cuenta"),
|
||||
|
@ -102,8 +108,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"backup": MessageLookupByLibrary.simpleMessage("Copia De Seguridad"),
|
||||
"backupAllAccounts":
|
||||
MessageLookupByLibrary.simpleMessage("Backup All Accounts"),
|
||||
"backupDataRequiredForRestore": MessageLookupByLibrary.simpleMessage(
|
||||
"Copia De Seguridad - Requerido Para Restaurar"),
|
||||
"backupDataRequiredForRestore": m2,
|
||||
"backupEncryptionKey":
|
||||
MessageLookupByLibrary.simpleMessage("Backup Encryption Key"),
|
||||
"backupWarning": MessageLookupByLibrary.simpleMessage(
|
||||
|
@ -125,7 +130,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"¿Está SEGURO de que desea BORRAR esta cuenta? DEBE tener una COPIA DE SEGURIDAD para recuperarla. Esta operación NO es reversible."),
|
||||
"confirmResetApp": MessageLookupByLibrary.simpleMessage(
|
||||
"¿Seguro que quieres restablecer la aplicación? Sus cuentas NO serán eliminadas"),
|
||||
"confirmSignATransactionToAddressFor": m2,
|
||||
"confirmSignATransactionToAddressFor": m3,
|
||||
"confirmSigning":
|
||||
MessageLookupByLibrary.simpleMessage("Confirm Signing"),
|
||||
"confs": MessageLookupByLibrary.simpleMessage("Confs"),
|
||||
|
@ -151,10 +156,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"doYouWantToDeleteTheSecretKeyAndConvert":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"¿Quiere BORRAR la clave secreta y convertir esta cuenta a solo lectura? Ya no podrá gastar desde este dispositivo. Esta operación NO es reversible."),
|
||||
"doYouWantToTransferYourEntireTransparentBalanceTo": m3,
|
||||
"doYouWantToTransferYourEntireTransparentBalanceTo": m4,
|
||||
"duplicateAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Cuenta duplicada"),
|
||||
"encryptedBackup": m4,
|
||||
"encryptedBackup": m5,
|
||||
"enterSecretShareIfAccountIsMultisignature":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Enter secret share if account is multi-signature"),
|
||||
|
@ -198,7 +203,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"nameIsEmpty": MessageLookupByLibrary.simpleMessage("Nombre vacio"),
|
||||
"newSnapAddress":
|
||||
MessageLookupByLibrary.simpleMessage("Nueva Dirección Instantánea"),
|
||||
"newSubAccount":
|
||||
MessageLookupByLibrary.simpleMessage("New Sub Account"),
|
||||
"noAccount": MessageLookupByLibrary.simpleMessage("Sin Cuenta"),
|
||||
"noActiveAccount":
|
||||
MessageLookupByLibrary.simpleMessage("No active account"),
|
||||
"noAuthenticationMethod":
|
||||
MessageLookupByLibrary.simpleMessage("Sin método de autenticación"),
|
||||
"noContacts": MessageLookupByLibrary.simpleMessage("Sin Contactos"),
|
||||
|
@ -235,7 +244,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"purple": MessageLookupByLibrary.simpleMessage("Morada"),
|
||||
"qty": MessageLookupByLibrary.simpleMessage("Cantidad"),
|
||||
"realized": MessageLookupByLibrary.simpleMessage("Dio Cuenta"),
|
||||
"receive": m6,
|
||||
"receive": m7,
|
||||
"receivePayment":
|
||||
MessageLookupByLibrary.simpleMessage("Recibir un pago"),
|
||||
"rescan": MessageLookupByLibrary.simpleMessage("Escanear"),
|
||||
|
@ -266,11 +275,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage(
|
||||
"Seleccionar Notas a EXCLUIR de los pagos"),
|
||||
"send": MessageLookupByLibrary.simpleMessage("Enviar"),
|
||||
"sendCointicker": m7,
|
||||
"sendCointickerTo": m8,
|
||||
"sendFrom": m9,
|
||||
"sendingATotalOfAmountCointickerToCountRecipients": m10,
|
||||
"sendingAzecCointickerToAddress": m11,
|
||||
"sendCointicker": m8,
|
||||
"sendCointickerTo": m9,
|
||||
"sendFrom": m10,
|
||||
"sendingATotalOfAmountCointickerToCountRecipients": m11,
|
||||
"sendingAzecCointickerToAddress": m12,
|
||||
"server": MessageLookupByLibrary.simpleMessage("Servidor"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Ajustes"),
|
||||
"shieldTranspBalance":
|
||||
|
@ -281,10 +290,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage("Blindaje en progreso…"),
|
||||
"sign": MessageLookupByLibrary.simpleMessage("Sign"),
|
||||
"simple": MessageLookupByLibrary.simpleMessage("Sencillo"),
|
||||
"simpleMode": MessageLookupByLibrary.simpleMessage("Simple Mode"),
|
||||
"spendable": MessageLookupByLibrary.simpleMessage("Gastable"),
|
||||
"spendableBalance":
|
||||
MessageLookupByLibrary.simpleMessage("Saldo Gastable"),
|
||||
"splitAccount": MessageLookupByLibrary.simpleMessage("Split Account"),
|
||||
"subAccountOf": m13,
|
||||
"synching": MessageLookupByLibrary.simpleMessage("Sincronizando"),
|
||||
"table": MessageLookupByLibrary.simpleMessage("Lista"),
|
||||
"tapAnIconToShowTheQrCode": MessageLookupByLibrary.simpleMessage(
|
||||
|
@ -298,7 +309,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"Pinchar QR para Dirección Transparente"),
|
||||
"tapTransactionForDetails": MessageLookupByLibrary.simpleMessage(
|
||||
"Toque Transacción para detalles"),
|
||||
"textCopiedToClipboard": m12,
|
||||
"textCopiedToClipboard": m14,
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Tema"),
|
||||
"themeEditor": MessageLookupByLibrary.simpleMessage("Editora de temas"),
|
||||
"thisAccountAlreadyExists":
|
||||
|
@ -327,7 +338,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage("Saldo sin blindaje"),
|
||||
"unsignedTransactionFile": MessageLookupByLibrary.simpleMessage(
|
||||
"Archivo de transaccion sin firmar"),
|
||||
"useSettingscurrency": m13,
|
||||
"useSettingscurrency": m15,
|
||||
"useTransparentBalance":
|
||||
MessageLookupByLibrary.simpleMessage("Usar Saldo Transp"),
|
||||
"useUa": MessageLookupByLibrary.simpleMessage("Usar UA"),
|
||||
|
|
|
@ -25,31 +25,36 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
static String m1(ticker) =>
|
||||
"Voulez vous sauver vos contacts? Ceci coute 0.01 m${ticker}";
|
||||
|
||||
static String m2(address, amount) =>
|
||||
static String m2(name) =>
|
||||
"Données de sauvegarde - ${name} - requises pour la restauration";
|
||||
|
||||
static String m3(address, amount) =>
|
||||
"Do you want to sign a transaction to ${address} for ${amount}";
|
||||
|
||||
static String m3(ticker) =>
|
||||
static String m4(ticker) =>
|
||||
"Voulez-vous transférer l\'intégralité de votre solde transparent à votre adresse protégée?";
|
||||
|
||||
static String m4(app) => "Sauvegarde de ${app} ";
|
||||
static String m5(app) => "Sauvegarde de ${app} ";
|
||||
|
||||
static String m6(ticker) => "Recevoir ${ticker}";
|
||||
static String m7(ticker) => "Recevoir ${ticker}";
|
||||
|
||||
static String m7(ticker) => "Envoyer ${ticker}";
|
||||
static String m8(ticker) => "Envoyer ${ticker}";
|
||||
|
||||
static String m8(ticker) => "Envoyer ${ticker} à...";
|
||||
static String m9(ticker) => "Envoyer ${ticker} à...";
|
||||
|
||||
static String m9(app) => "Envoyé via ${app}";
|
||||
static String m10(app) => "Envoyé via ${app}";
|
||||
|
||||
static String m10(amount, ticker, count) =>
|
||||
static String m11(amount, ticker, count) =>
|
||||
"Envoi d\'un total de ${amount} ${ticker} à ${count} destinataires";
|
||||
|
||||
static String m11(aZEC, ticker, address) =>
|
||||
static String m12(aZEC, ticker, address) =>
|
||||
"Envoi de ${aZEC} ${ticker} à ${address}";
|
||||
|
||||
static String m12(text) => "${text} copied to clipboard";
|
||||
static String m13(name) => "Sous Compte de ${name}";
|
||||
|
||||
static String m13(currency) => "Utiliser ${currency}";
|
||||
static String m14(text) => "${text} copied to clipboard";
|
||||
|
||||
static String m15(currency) => "Utiliser ${currency}";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
|
@ -66,6 +71,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"accountHasSomeBalanceAreYouSureYouWantTo":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Ce compte a un solde. Voulez vous l\'effacer?"),
|
||||
"accountIndex": MessageLookupByLibrary.simpleMessage("Index du Compte"),
|
||||
"accountName": MessageLookupByLibrary.simpleMessage("Nom du compte"),
|
||||
"accountNameIsRequired":
|
||||
MessageLookupByLibrary.simpleMessage("Le nom du compte est requis"),
|
||||
|
@ -104,8 +110,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"backup": MessageLookupByLibrary.simpleMessage("Sauvegarde"),
|
||||
"backupAllAccounts":
|
||||
MessageLookupByLibrary.simpleMessage("Sauver tous les comptes"),
|
||||
"backupDataRequiredForRestore": MessageLookupByLibrary.simpleMessage(
|
||||
"Données de sauvegarde - requises pour la restauration"),
|
||||
"backupDataRequiredForRestore": m2,
|
||||
"backupEncryptionKey":
|
||||
MessageLookupByLibrary.simpleMessage("Clé de chiffrage"),
|
||||
"backupWarning": MessageLookupByLibrary.simpleMessage(
|
||||
|
@ -127,7 +132,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"Êtes-vous SUR de vouloir SUPPRIMER ce compte ? Vous DEVEZ avoir une SAUVEGARDE pour le récupérer. Cette opération n\'est PAS réversible."),
|
||||
"confirmResetApp": MessageLookupByLibrary.simpleMessage(
|
||||
"Etes vous sur de vouloir réinitialiser. Vos comptes ne seront PAS perdus "),
|
||||
"confirmSignATransactionToAddressFor": m2,
|
||||
"confirmSignATransactionToAddressFor": m3,
|
||||
"confirmSigning":
|
||||
MessageLookupByLibrary.simpleMessage("Confirm Signing"),
|
||||
"confs": MessageLookupByLibrary.simpleMessage("Confs"),
|
||||
|
@ -153,10 +158,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"doYouWantToDeleteTheSecretKeyAndConvert":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Voulez-vous SUPPRIMER la clé secrète et convertir ce compte en un compte d\'observation ? Vous ne pourrez plus dépenser depuis cet appareil. Cette opération n\'est PAS réversible."),
|
||||
"doYouWantToTransferYourEntireTransparentBalanceTo": m3,
|
||||
"doYouWantToTransferYourEntireTransparentBalanceTo": m4,
|
||||
"duplicateAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Compte en double"),
|
||||
"encryptedBackup": m4,
|
||||
"encryptedBackup": m5,
|
||||
"enterSecretShareIfAccountIsMultisignature":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Enter secret share if account is multi-signature"),
|
||||
|
@ -202,7 +207,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"nameIsEmpty": MessageLookupByLibrary.simpleMessage("Le nom est vide"),
|
||||
"newSnapAddress": MessageLookupByLibrary.simpleMessage(
|
||||
"Nouvelle adresse instantanée"),
|
||||
"newSubAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Nouveau Sous Compte"),
|
||||
"noAccount": MessageLookupByLibrary.simpleMessage("Pas de compte"),
|
||||
"noActiveAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Aucun Compte sélectionné"),
|
||||
"noAuthenticationMethod": MessageLookupByLibrary.simpleMessage(
|
||||
"Pas de méthode d\'authentification"),
|
||||
"noContacts": MessageLookupByLibrary.simpleMessage("Pas de Contacts"),
|
||||
|
@ -240,7 +249,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"purple": MessageLookupByLibrary.simpleMessage("Violet"),
|
||||
"qty": MessageLookupByLibrary.simpleMessage("Quantité"),
|
||||
"realized": MessageLookupByLibrary.simpleMessage("Réalisé"),
|
||||
"receive": m6,
|
||||
"receive": m7,
|
||||
"receivePayment":
|
||||
MessageLookupByLibrary.simpleMessage("Recevoir un payment"),
|
||||
"rescan": MessageLookupByLibrary.simpleMessage("Parcourir à nouveau"),
|
||||
|
@ -271,11 +280,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage(
|
||||
"Sélectionnez les billets à EXCLURE des paiements"),
|
||||
"send": MessageLookupByLibrary.simpleMessage("Envoyer"),
|
||||
"sendCointicker": m7,
|
||||
"sendCointickerTo": m8,
|
||||
"sendFrom": m9,
|
||||
"sendingATotalOfAmountCointickerToCountRecipients": m10,
|
||||
"sendingAzecCointickerToAddress": m11,
|
||||
"sendCointicker": m8,
|
||||
"sendCointickerTo": m9,
|
||||
"sendFrom": m10,
|
||||
"sendingATotalOfAmountCointickerToCountRecipients": m11,
|
||||
"sendingAzecCointickerToAddress": m12,
|
||||
"server": MessageLookupByLibrary.simpleMessage("Serveur"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Paramètres"),
|
||||
"shieldTranspBalance": MessageLookupByLibrary.simpleMessage(
|
||||
|
@ -286,10 +295,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage("Masquage en cours..."),
|
||||
"sign": MessageLookupByLibrary.simpleMessage("Sign"),
|
||||
"simple": MessageLookupByLibrary.simpleMessage("Simple"),
|
||||
"simpleMode": MessageLookupByLibrary.simpleMessage("Mode Simple"),
|
||||
"spendable": MessageLookupByLibrary.simpleMessage("Dépensable"),
|
||||
"spendableBalance":
|
||||
MessageLookupByLibrary.simpleMessage("Montant dépensable"),
|
||||
"splitAccount": MessageLookupByLibrary.simpleMessage("Split Account"),
|
||||
"subAccountOf": m13,
|
||||
"synching":
|
||||
MessageLookupByLibrary.simpleMessage("Synchronisation en cours"),
|
||||
"table": MessageLookupByLibrary.simpleMessage("Tableau"),
|
||||
|
@ -304,7 +315,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"Appuyez sur le code QR pour l\'adresse transparente"),
|
||||
"tapTransactionForDetails": MessageLookupByLibrary.simpleMessage(
|
||||
"Presser sur une Transaction pour plus de details"),
|
||||
"textCopiedToClipboard": m12,
|
||||
"textCopiedToClipboard": m14,
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Thème"),
|
||||
"themeEditor": MessageLookupByLibrary.simpleMessage("Editeur de Thème"),
|
||||
"thisAccountAlreadyExists":
|
||||
|
@ -333,7 +344,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
MessageLookupByLibrary.simpleMessage("Solde Transparent"),
|
||||
"unsignedTransactionFile": MessageLookupByLibrary.simpleMessage(
|
||||
"Fichier de transaction non signée"),
|
||||
"useSettingscurrency": m13,
|
||||
"useSettingscurrency": m15,
|
||||
"useTransparentBalance": MessageLookupByLibrary.simpleMessage(
|
||||
"Utiliser le Solde Transparent"),
|
||||
"useUa": MessageLookupByLibrary.simpleMessage("Utiliser UA"),
|
||||
|
|
|
@ -630,13 +630,13 @@ class S {
|
|||
);
|
||||
}
|
||||
|
||||
/// `Backup Data - Required for Restore`
|
||||
String get backupDataRequiredForRestore {
|
||||
/// `Backup Data - {name} - Required for Restore`
|
||||
String backupDataRequiredForRestore(Object name) {
|
||||
return Intl.message(
|
||||
'Backup Data - Required for Restore',
|
||||
'Backup Data - $name - Required for Restore',
|
||||
name: 'backupDataRequiredForRestore',
|
||||
desc: '',
|
||||
args: [],
|
||||
args: [name],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1991,6 +1991,56 @@ class S {
|
|||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Simple Mode`
|
||||
String get simpleMode {
|
||||
return Intl.message(
|
||||
'Simple Mode',
|
||||
name: 'simpleMode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Account Index`
|
||||
String get accountIndex {
|
||||
return Intl.message(
|
||||
'Account Index',
|
||||
name: 'accountIndex',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sub Account of {name}`
|
||||
String subAccountOf(Object name) {
|
||||
return Intl.message(
|
||||
'Sub Account of $name',
|
||||
name: 'subAccountOf',
|
||||
desc: '',
|
||||
args: [name],
|
||||
);
|
||||
}
|
||||
|
||||
/// `New Sub Account`
|
||||
String get newSubAccount {
|
||||
return Intl.message(
|
||||
'New Sub Account',
|
||||
name: 'newSubAccount',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `No active account`
|
||||
String get noActiveAccount {
|
||||
return Intl.message(
|
||||
'No active account',
|
||||
name: 'noActiveAccount',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||
|
|
|
@ -6,9 +6,7 @@ import 'main.dart';
|
|||
import 'generated/l10n.dart';
|
||||
|
||||
class HistoryWidget extends StatefulWidget {
|
||||
final void Function(int index) tabTo;
|
||||
|
||||
HistoryWidget(this.tabTo);
|
||||
HistoryWidget();
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => HistoryState();
|
||||
|
@ -43,47 +41,47 @@ class HistoryState extends State<HistoryWidget>
|
|||
}),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).datetime +
|
||||
accountManager.txSortConfig.getIndicator("time")),
|
||||
active.txSortConfig.getIndicator("time")),
|
||||
onSort: (_, __) {
|
||||
setState(() {
|
||||
accountManager.sortTx("time");
|
||||
active.sortTx("time");
|
||||
});
|
||||
},
|
||||
),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).amount +
|
||||
accountManager.txSortConfig.getIndicator("amount")),
|
||||
active.txSortConfig.getIndicator("amount")),
|
||||
numeric: true,
|
||||
onSort: (_, __) {
|
||||
setState(() {
|
||||
accountManager.sortTx("amount");
|
||||
active.sortTx("amount");
|
||||
});
|
||||
},
|
||||
),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).txId +
|
||||
accountManager.txSortConfig.getIndicator("txid")),
|
||||
active.txSortConfig.getIndicator("txid")),
|
||||
onSort: (_, __) {
|
||||
setState(() {
|
||||
accountManager.sortTx("txid");
|
||||
active.sortTx("txid");
|
||||
});
|
||||
},
|
||||
),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).address +
|
||||
accountManager.txSortConfig.getIndicator("address")),
|
||||
active.txSortConfig.getIndicator("address")),
|
||||
onSort: (_, __) {
|
||||
setState(() {
|
||||
accountManager.sortTx("address");
|
||||
active.sortTx("address");
|
||||
});
|
||||
},
|
||||
),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).memo +
|
||||
accountManager.txSortConfig.getIndicator("memo")),
|
||||
active.txSortConfig.getIndicator("memo")),
|
||||
onSort: (_, __) {
|
||||
setState(() {
|
||||
accountManager.sortTx("memo");
|
||||
active.sortTx("memo");
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -101,7 +99,7 @@ class HistoryState extends State<HistoryWidget>
|
|||
}
|
||||
|
||||
_onExport() async {
|
||||
final csvData = accountManager.sortedTxs.map((tx) => [
|
||||
final csvData = active.sortedTxs.map((tx) => [
|
||||
tx.fullTxId, tx.height, tx.timestamp, tx.address, tx.contact ?? '',
|
||||
tx.value, tx.memo]).toList();
|
||||
await shareCsv(csvData, 'tx_history.csv', S.of(context).transactionHistory);
|
||||
|
@ -115,7 +113,7 @@ class HistoryDataSource extends DataTableSource {
|
|||
|
||||
@override
|
||||
DataRow getRow(int index) {
|
||||
final tx = accountManager.sortedTxs[index];
|
||||
final tx = active.sortedTxs[index];
|
||||
final confsOrHeight = settings.showConfirmations
|
||||
? syncStatus.latestHeight - tx.height + 1
|
||||
: tx.height;
|
||||
|
@ -144,7 +142,7 @@ class HistoryDataSource extends DataTableSource {
|
|||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get rowCount => accountManager.txs.length;
|
||||
int get rowCount => active.txs.length;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => 0;
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
|
||||
import 'about.dart';
|
||||
import 'account.dart';
|
||||
import 'account_manager.dart';
|
||||
import 'budget.dart';
|
||||
import 'contact.dart';
|
||||
import 'history.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'main.dart';
|
||||
import 'note.dart';
|
||||
import 'store.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (context) {
|
||||
final simpleMode = settings.simpleMode;
|
||||
return HomePageInner(simpleMode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class HomePageInner extends StatefulWidget {
|
||||
final bool simpleMode;
|
||||
HomePageInner(this.simpleMode);
|
||||
@override
|
||||
HomeState createState() => HomeState();
|
||||
}
|
||||
|
||||
class HomeState extends State<HomePageInner> with TickerProviderStateMixin {
|
||||
TabController? _tabController;
|
||||
int _tabIndex = 0;
|
||||
StreamSubscription? _syncDispose;
|
||||
final contactKey = GlobalKey<ContactsState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.microtask(() async {
|
||||
await syncStatus.update();
|
||||
await active.updateBalances();
|
||||
await priceStore.updateChart();
|
||||
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
await syncStatus.sync();
|
||||
await contacts.fetchContacts();
|
||||
|
||||
Timer.periodic(Duration(seconds: 15), (Timer t) async {
|
||||
syncStatus.sync();
|
||||
await active.updateBalances();
|
||||
});
|
||||
Timer.periodic(Duration(minutes: 5), (Timer t) async {
|
||||
await priceStore.updateChart();
|
||||
});
|
||||
});
|
||||
_syncDispose = syncStream.listen((height) {
|
||||
final h = height as int?;
|
||||
if (h != null) {
|
||||
syncStatus.setSyncHeight(h);
|
||||
eta.checkpoint(h, DateTime.now());
|
||||
} else {
|
||||
WarpApi.mempoolReset(active.coin, syncStatus.latestHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_tabController?.dispose();
|
||||
final tabController = TabController(length: settings.simpleMode ? 3 : 6, vsync: this);
|
||||
tabController.addListener(() {
|
||||
setState(() {
|
||||
_tabIndex = tabController.index;
|
||||
});
|
||||
});
|
||||
_tabController = tabController;
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_syncDispose?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Observer(builder: (context) {
|
||||
final s = S.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final simpleMode = settings.simpleMode;
|
||||
|
||||
if (active.id == 0) {
|
||||
return AccountManagerPage();
|
||||
}
|
||||
|
||||
final contactTabIndex = simpleMode ? 2 : 5;
|
||||
Widget button = Container();
|
||||
if (_tabIndex == 0)
|
||||
button = FloatingActionButton(
|
||||
onPressed: _onSend,
|
||||
backgroundColor: theme.colorScheme.secondary,
|
||||
child: Icon(Icons.send),
|
||||
);
|
||||
else if (_tabIndex == contactTabIndex)
|
||||
button = FloatingActionButton(
|
||||
onPressed: _onAddContact,
|
||||
backgroundColor: theme.colorScheme.secondary,
|
||||
child: Icon(Icons.add),
|
||||
);
|
||||
|
||||
final menu = PopupMenuButton<String>(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(child: Text(s.accounts), value: "Accounts"),
|
||||
PopupMenuItem(child: Text(s.backup), value: "Backup"),
|
||||
PopupMenuItem(child: Text(s.rescan), value: "Rescan"),
|
||||
if (!simpleMode && active.canPay)
|
||||
PopupMenuItem(child: Text(s.coldStorage), value: "Cold"),
|
||||
if (!simpleMode)
|
||||
PopupMenuItem(child: Text(s.multipay), value: "MultiPay"),
|
||||
if (!simpleMode)
|
||||
PopupMenuItem(child: Text(s.broadcast), value: "Broadcast"),
|
||||
PopupMenuItem(child: Text(s.settings), value: "Settings"),
|
||||
PopupMenuItem(child: Text(s.help), value: "Help"),
|
||||
PopupMenuItem(child: Text(s.about), value: "About"),
|
||||
];
|
||||
},
|
||||
onSelected: _onMenu,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text("${active.account.name}"),
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
isScrollable: true,
|
||||
tabs: [
|
||||
Tab(text: s.account),
|
||||
if (!simpleMode) Tab(text: s.notes),
|
||||
Tab(text: s.history),
|
||||
if (!simpleMode) Tab(text: s.budget),
|
||||
if (!simpleMode) Tab(text: s.tradingPl),
|
||||
Tab(text: s.contacts),
|
||||
],
|
||||
),
|
||||
actions: [menu],
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
AccountPage2(),
|
||||
if (!simpleMode) NoteWidget(),
|
||||
HistoryWidget(),
|
||||
if (!simpleMode) BudgetWidget(),
|
||||
if (!simpleMode) PnLWidget(),
|
||||
ContactsTab(key: contactKey),
|
||||
],
|
||||
),
|
||||
floatingActionButton: button,
|
||||
);
|
||||
});
|
||||
|
||||
_onSend() {
|
||||
Navigator.of(this.context).pushNamed('/send');
|
||||
}
|
||||
|
||||
_onMenu(String choice) {
|
||||
switch (choice) {
|
||||
case "Accounts":
|
||||
Navigator.of(this.context).pushNamed('/accounts');
|
||||
break;
|
||||
case "Backup":
|
||||
_backup();
|
||||
break;
|
||||
case "Rescan":
|
||||
_rescan();
|
||||
break;
|
||||
case "Cold":
|
||||
_cold();
|
||||
break;
|
||||
case "MultiPay":
|
||||
_multiPay();
|
||||
break;
|
||||
case "Broadcast":
|
||||
_broadcast();
|
||||
break;
|
||||
case "Settings":
|
||||
_settings();
|
||||
break;
|
||||
case "Help":
|
||||
launch(DOC_URL);
|
||||
break;
|
||||
case "About":
|
||||
showAbout(this.context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_backup() async {
|
||||
final didAuthenticate = await authenticate(context, S.of(context).pleaseAuthenticateToShowAccountSeed);
|
||||
if (didAuthenticate) {
|
||||
Navigator.of(context).pushNamed('/backup');
|
||||
}
|
||||
}
|
||||
|
||||
_rescan() async {
|
||||
final approved = await rescanDialog(context);
|
||||
if (approved) {
|
||||
syncStatus.rescan(context);
|
||||
}
|
||||
}
|
||||
|
||||
_cold() {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(S.of(context).coldStorage),
|
||||
content:
|
||||
Text(S.of(context).doYouWantToDeleteTheSecretKeyAndConvert),
|
||||
actions: confirmButtons(context, _convertToWatchOnly,
|
||||
okLabel: S.of(context).delete)));
|
||||
}
|
||||
|
||||
_multiPay() {
|
||||
Navigator.of(context).pushNamed('/multipay');
|
||||
}
|
||||
|
||||
_broadcast() async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
|
||||
if (result != null) {
|
||||
final res = WarpApi.broadcast(active.coin, result.files.single.path!);
|
||||
final snackBar = SnackBar(content: Text(res));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
||||
_convertToWatchOnly() async {
|
||||
await active.convertToWatchOnly();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
_settings() {
|
||||
Navigator.of(context).pushNamed('/settings');
|
||||
}
|
||||
|
||||
_onAddContact() async {
|
||||
final contact = await contactKey.currentState
|
||||
?.showContactForm(context, Contact.empty());
|
||||
if (contact != null) {
|
||||
contacts.add(contact);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@
|
|||
"seed": "Seed",
|
||||
"confirmDeleteAccount": "Are you SURE you want to DELETE this account? You MUST have a BACKUP to recover it. This operation is NOT reversible.",
|
||||
"changeAccountName": "Change Account Name",
|
||||
"backupDataRequiredForRestore": "Backup Data - Required for Restore",
|
||||
"backupDataRequiredForRestore": "Backup Data - {name} - Required for Restore",
|
||||
"secretKey": "Secret Key",
|
||||
"viewingKey": "Viewing Key",
|
||||
"tapAnIconToShowTheQrCode": "Tap an icon to show the QR code",
|
||||
|
@ -192,5 +192,10 @@
|
|||
"encryptedBackup": "{app} Encrypted Backup",
|
||||
"fullRestore": "Full Restore",
|
||||
"loadBackup": "Load Backup",
|
||||
"backupAllAccounts": "Backup All Accounts"
|
||||
"backupAllAccounts": "Backup All Accounts",
|
||||
"simpleMode": "Simple Mode",
|
||||
"accountIndex": "Account Index",
|
||||
"subAccountOf": "Sub Account of {name}",
|
||||
"newSubAccount": "New Sub Account",
|
||||
"noActiveAccount": "No active account"
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
"seed":"Semilla",
|
||||
"confirmDeleteAccount":"¿Está SEGURO de que desea BORRAR esta cuenta? DEBE tener una COPIA DE SEGURIDAD para recuperarla. Esta operación NO es reversible.",
|
||||
"changeAccountName":"Cambiar nombre de la cuenta",
|
||||
"backupDataRequiredForRestore":"Copia De Seguridad - Requerido Para Restaurar",
|
||||
"backupDataRequiredForRestore":"Copia De Seguridad - {name} - Requerido Para Restaurar",
|
||||
"secretKey":"Llave Secreta",
|
||||
"viewingKey":"Llave Lectura",
|
||||
"tapAnIconToShowTheQrCode":"Pinchar icono para mostrar código QR",
|
||||
|
@ -190,5 +190,10 @@
|
|||
"encryptedBackup": "{app} Encrypted Backup",
|
||||
"fullRestore": "Full Restore",
|
||||
"loadBackup": "Load Backup",
|
||||
"backupAllAccounts": "Backup All Accounts"
|
||||
"backupAllAccounts": "Backup All Accounts",
|
||||
"simpleMode": "Simple Mode",
|
||||
"accountIndex": "Account Index",
|
||||
"subAccountOf": "Sub Account of {name}",
|
||||
"newSubAccount": "New Sub Account",
|
||||
"noActiveAccount": "No active account"
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
"seed": "Graine",
|
||||
"confirmDeleteAccount": "Êtes-vous SUR de vouloir SUPPRIMER ce compte ? Vous DEVEZ avoir une SAUVEGARDE pour le récupérer. Cette opération n'est PAS réversible.",
|
||||
"changeAccountName": "Modifier le nom du compte",
|
||||
"backupDataRequiredForRestore": "Données de sauvegarde - requises pour la restauration",
|
||||
"backupDataRequiredForRestore": "Données de sauvegarde - {name} - requises pour la restauration",
|
||||
"secretKey": "Clé secrète",
|
||||
"viewingKey": "Affichage de la clé",
|
||||
"tapAnIconToShowTheQrCode": "Appuyez sur une icône pour afficher le code QR",
|
||||
|
@ -190,5 +190,10 @@
|
|||
"encryptedBackup": "Sauvegarde de {app} ",
|
||||
"fullRestore": "Restauration complète",
|
||||
"loadBackup": "Recharger une sauvegarde",
|
||||
"backupAllAccounts": "Sauver tous les comptes"
|
||||
"backupAllAccounts": "Sauver tous les comptes",
|
||||
"simpleMode": "Mode Simple",
|
||||
"accountIndex": "Index du Compte",
|
||||
"subAccountOf": "Sous Compte de {name}",
|
||||
"newSubAccount": "Nouveau Sous Compte",
|
||||
"noActiveAccount": "Aucun Compte sélectionné"
|
||||
}
|
||||
|
|
154
lib/main.dart
154
lib/main.dart
|
@ -12,7 +12,6 @@ import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
|
|||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:rate_my_app/rate_my_app.dart';
|
||||
|
@ -23,15 +22,15 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:quick_actions/quick_actions.dart';
|
||||
import 'package:flutter/services.dart' show PlatformException;
|
||||
import 'accounts.dart';
|
||||
import 'coin/coins.dart';
|
||||
import 'generated/l10n.dart';
|
||||
|
||||
import 'account.dart';
|
||||
import 'account_manager.dart';
|
||||
import 'backup.dart';
|
||||
import 'coin/coindef.dart';
|
||||
import 'home.dart';
|
||||
import 'multisend.dart';
|
||||
import 'multisign.dart';
|
||||
// import 'multisign.dart';
|
||||
import 'payment_uri.dart';
|
||||
import 'reset.dart';
|
||||
import 'settings.dart';
|
||||
|
@ -41,58 +40,75 @@ import 'store.dart';
|
|||
import 'theme_editor.dart';
|
||||
import 'transaction.dart';
|
||||
|
||||
var coin = Coin();
|
||||
|
||||
const ZECUNIT = 100000000.0;
|
||||
var ZECUNIT_DECIMAL = Decimal.parse('100000000');
|
||||
const mZECUNIT = 100000;
|
||||
const DEFAULT_FEE = 1000;
|
||||
const MAXMONEY = 21000000;
|
||||
const DOC_URL = "https://hhanh00.github.io/zwallet/";
|
||||
const APP_NAME = "ZYWallet";
|
||||
|
||||
var accountManager = AccountManager();
|
||||
// var accountManager = AccountManager();
|
||||
var priceStore = PriceStore();
|
||||
var syncStatus = SyncStatus();
|
||||
var settings = Settings();
|
||||
var multipayData = MultiPayStore();
|
||||
var eta = ETAStore();
|
||||
var contacts = ContactStore();
|
||||
|
||||
Future<Database> getDatabase() async {
|
||||
var databasesPath = await getDatabasesPath();
|
||||
final path = join(databasesPath, 'zec.db');
|
||||
var db = await openDatabase(path);
|
||||
return db;
|
||||
}
|
||||
var accounts = AccountManager2();
|
||||
var active = ActiveAccount();
|
||||
|
||||
StreamSubscription? subUniLinks;
|
||||
|
||||
void handleUri(BuildContext context, Uri uri) {
|
||||
final scheme = uri.scheme;
|
||||
final coinDef = settings.coins.firstWhere((c) => c.def.currency == scheme);
|
||||
final id = settings.coins[coinDef.coin].active;
|
||||
if (id != 0) {
|
||||
active.setActiveAccount(coinDef.coin, id);
|
||||
Navigator.of(context).pushNamed(
|
||||
'/send', arguments: SendPageArgs(uri: uri.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initUniLinks(BuildContext context) async {
|
||||
try {
|
||||
final initialLink = await getInitialLink();
|
||||
if (initialLink != null)
|
||||
Navigator.of(context).pushNamed(
|
||||
'/send', arguments: SendPageArgs(uri: initialLink));
|
||||
final uri = await getInitialUri();
|
||||
if (uri != null) {
|
||||
handleUri(context, uri);
|
||||
}
|
||||
} on PlatformException {}
|
||||
|
||||
subUniLinks = linkStream.listen((String? uri) {
|
||||
Navigator.of(context).pushNamed('/send', arguments: SendPageArgs(uri: uri));
|
||||
subUniLinks = linkStream.listen((String? uriString) {
|
||||
if (uriString == null) return;
|
||||
final uri = Uri.parse(uriString);
|
||||
handleUri(context, uri);
|
||||
});
|
||||
}
|
||||
|
||||
void handleQuickAction(BuildContext context, String shortcut) {
|
||||
switch (shortcut) {
|
||||
case 'receive':
|
||||
Navigator.of(context).pushNamed('/receive');
|
||||
break;
|
||||
case 'send':
|
||||
Navigator.of(context).pushNamed('/send');
|
||||
break;
|
||||
void handleQuickAction(BuildContext context, String type) {
|
||||
final t = type.split(".");
|
||||
final coin = int.parse(t[0]);
|
||||
final shortcut = t[1];
|
||||
final a = settings.coins[coin].active;
|
||||
|
||||
if (a != 0) {
|
||||
Future.microtask(() async {
|
||||
await active.setActiveAccount(coin, a);
|
||||
switch (shortcut) {
|
||||
case 'receive':
|
||||
Navigator.of(context).pushNamed('/receive');
|
||||
break;
|
||||
case 'send':
|
||||
Navigator.of(context).pushNamed('/send');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LoadProgress extends StatelessWidget {
|
||||
double value;
|
||||
final double value;
|
||||
|
||||
LoadProgress(this.value);
|
||||
|
||||
|
@ -134,7 +150,8 @@ void main() {
|
|||
headingRowColor: MaterialStateColor.resolveWith(
|
||||
(_) => settings.themeData.highlightColor)));
|
||||
return MaterialApp(
|
||||
title: coin.app,
|
||||
title: APP_NAME,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: theme,
|
||||
home: home,
|
||||
scaffoldMessengerKey: rootScaffoldMessengerKey,
|
||||
|
@ -147,7 +164,7 @@ void main() {
|
|||
supportedLocales: S.delegate.supportedLocales,
|
||||
onGenerateRoute: (RouteSettings routeSettings) {
|
||||
var routes = <String, WidgetBuilder>{
|
||||
'/account': (context) => AccountPage(),
|
||||
'/account': (context) => HomePage(),
|
||||
'/restore': (context) => RestorePage(),
|
||||
'/send': (context) =>
|
||||
SendPage(routeSettings.arguments as SendPageArgs?),
|
||||
|
@ -158,13 +175,13 @@ void main() {
|
|||
'/tx': (context) =>
|
||||
TransactionPage(routeSettings.arguments as Tx),
|
||||
'/backup': (context) =>
|
||||
BackupPage(routeSettings.arguments as int?),
|
||||
BackupPage(routeSettings.arguments as AccountId?),
|
||||
'/multipay': (context) => MultiPayPage(),
|
||||
'/multisig': (context) => MultisigPage(),
|
||||
'/multisign': (context) => MultisigAggregatorPage(
|
||||
routeSettings.arguments as TxSummary),
|
||||
'/multisig_shares': (context) =>
|
||||
MultisigSharesPage(routeSettings.arguments as String),
|
||||
// '/multisig': (context) => MultisigPage(),
|
||||
// '/multisign': (context) => MultisigAggregatorPage(
|
||||
// routeSettings.arguments as TxSummary),
|
||||
// '/multisig_shares': (context) =>
|
||||
// MultisigSharesPage(routeSettings.arguments as String),
|
||||
'/edit_theme': (context) =>
|
||||
ThemeEditorPage(onSaved: settings.updateCustomThemeColors),
|
||||
'/reset': (context) => ResetPage(),
|
||||
|
@ -185,6 +202,7 @@ class ZWalletApp extends StatefulWidget {
|
|||
|
||||
class ZWalletAppState extends State<ZWalletApp> {
|
||||
bool initialized = false;
|
||||
late Future<bool> init;
|
||||
|
||||
RateMyApp rateMyApp = RateMyApp(
|
||||
preferencesPrefix: 'rateMyApp_',
|
||||
|
@ -201,32 +219,49 @@ class ZWalletAppState extends State<ZWalletApp> {
|
|||
rateMyApp.showRateDialog(this.context);
|
||||
}
|
||||
});
|
||||
init = _init();
|
||||
}
|
||||
|
||||
Future<bool> _init(BuildContext context) async {
|
||||
final s = S.of(this.context);
|
||||
Future<bool> _init() async {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
final dbPath = await getDatabasesPath();
|
||||
WarpApi.initWallet(dbPath + "/zec.db", settings.getLWD());
|
||||
final db = await getDatabase();
|
||||
await accountManager.init(db);
|
||||
await contacts.init(db);
|
||||
await syncStatus.init();
|
||||
await initUniLinks(context);
|
||||
await ycash.open(dbPath);
|
||||
await zcash.open(dbPath);
|
||||
WarpApi.initWallet(dbPath);
|
||||
for (var s in settings.servers) {
|
||||
WarpApi.updateLWD(s.coin, s.getLWDUrl());
|
||||
}
|
||||
await accounts.refresh();
|
||||
await active.restore();
|
||||
await syncStatus.update();
|
||||
if (accounts.list.isEmpty) {
|
||||
for (var c in settings.coins) {
|
||||
syncStatus.markAsSynced(c.coin);
|
||||
}
|
||||
}
|
||||
|
||||
await initUniLinks(this.context);
|
||||
final quickActions = QuickActions();
|
||||
quickActions.initialize((type) {
|
||||
handleQuickAction(this.context, type);
|
||||
});
|
||||
if (!settings.linkHooksInitialized) {
|
||||
quickActions.setShortcutItems(<ShortcutItem>[
|
||||
ShortcutItem(type: 'receive',
|
||||
localizedTitle: s.receive(coin.ticker),
|
||||
icon: 'receive'),
|
||||
ShortcutItem(type: 'send',
|
||||
localizedTitle: s.sendCointicker(coin.ticker),
|
||||
icon: 'send'),
|
||||
]);
|
||||
Future.microtask(() {
|
||||
final s = S.of(this.context);
|
||||
List<ShortcutItem> shortcuts = [];
|
||||
for (var c in settings.coins) {
|
||||
final coin = c.coin;
|
||||
final ticker = c.def.ticker;
|
||||
shortcuts.add(ShortcutItem(type: '$coin.receive',
|
||||
localizedTitle: s.receive(ticker),
|
||||
icon: 'receive'));
|
||||
shortcuts.add(ShortcutItem(type: '$coin.send',
|
||||
localizedTitle: s.sendCointicker(ticker),
|
||||
icon: 'send'));
|
||||
}
|
||||
quickActions.setShortcutItems(shortcuts);
|
||||
});
|
||||
await settings.setLinkHooksInitialized();
|
||||
}
|
||||
}
|
||||
|
@ -236,12 +271,10 @@ class ZWalletAppState extends State<ZWalletApp> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _init(context),
|
||||
future: init,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) return LoadProgress(0.7);
|
||||
return accountManager.accounts.isNotEmpty
|
||||
? AccountPage() :
|
||||
AccountManagerPage();
|
||||
return HomePage();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -414,6 +447,7 @@ Color amountColor(BuildContext context, num a) {
|
|||
|
||||
TextStyle fontWeight(TextStyle style, num v) {
|
||||
final value = v.abs();
|
||||
final coin = activeCoin();
|
||||
final style2 = style.copyWith(fontFeatures: [FontFeature.tabularFigures()]);
|
||||
if (value >= coin.weights[2])
|
||||
return style2.copyWith(fontWeight: FontWeight.w800);
|
||||
|
@ -526,13 +560,13 @@ Future<void> shieldTAddr(BuildContext context) async {
|
|||
content: Text(S
|
||||
.of(context)
|
||||
.doYouWantToTransferYourEntireTransparentBalanceTo(
|
||||
coin.ticker)),
|
||||
activeCoin().ticker)),
|
||||
actions: confirmButtons(context, () async {
|
||||
final s = S.of(context);
|
||||
Navigator.of(context).pop();
|
||||
final snackBar1 = SnackBar(content: Text(s.shieldingInProgress));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar1);
|
||||
final txid = await WarpApi.shieldTAddr(accountManager.active.id);
|
||||
final txid = await WarpApi.shieldTAddr(active.coin, active.id);
|
||||
final snackBar2 = SnackBar(content: Text("${s.txId}: $txid"));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
|
||||
})),
|
||||
|
|
|
@ -6,7 +6,9 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:warp/store.dart';
|
||||
import 'coin/coins.dart';
|
||||
import 'settings.dart';
|
||||
import 'store.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'package:warp_api/types.dart';
|
||||
|
||||
|
@ -78,7 +80,7 @@ class MultiPayState extends State<MultiPayPage> {
|
|||
child: Text(S
|
||||
.of(context)
|
||||
.sendingATotalOfAmountCointickerToCountRecipients(
|
||||
amount, coin.ticker, count))),
|
||||
amount, activeCoin().ticker, count))),
|
||||
actions: confirmButtons(
|
||||
context, () => Navigator.of(context).pop(true),
|
||||
cancelValue: false)));
|
||||
|
@ -87,7 +89,7 @@ class MultiPayState extends State<MultiPayPage> {
|
|||
await send(context, multipayData.recipients, false);
|
||||
|
||||
multipayData.clear();
|
||||
await accountManager.fetchAccountData(true);
|
||||
await active.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ class MultisigAggregatorState extends State<MultisigAggregatorPage> {
|
|||
final tx = await WarpApi.submitTx(widget.txSummary.txJson, PORT);
|
||||
SnackBar snackBar;
|
||||
if (tx.startsWith("00")) { // first byte is success/error
|
||||
final txId = WarpApi.broadcastHex(tx.substring(2));
|
||||
final txId = WarpApi.broadcastHex(accountManager.coin, tx.substring(2));
|
||||
snackBar = SnackBar(content: Text("${s.txId}: $txId"));
|
||||
}
|
||||
else {
|
||||
|
@ -248,7 +248,7 @@ class MultisigState extends State<MultisigPage> {
|
|||
|
||||
_split() async {
|
||||
final s = S.of(context);
|
||||
final shareString = WarpApi.splitAccount(_threshold, _participants, accountManager.active.id);
|
||||
final shareString = WarpApi.splitAccount(accountManager.coin, _threshold, _participants, accountManager.active.id);
|
||||
Navigator.of(context).pushNamed('/multisig_shares', arguments: shareString);
|
||||
}
|
||||
|
|
@ -7,9 +7,7 @@ import 'store.dart';
|
|||
import 'generated/l10n.dart';
|
||||
|
||||
class NoteWidget extends StatefulWidget {
|
||||
final void Function(int index) tabTo;
|
||||
|
||||
NoteWidget(this.tabTo);
|
||||
NoteWidget();
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NoteState();
|
||||
|
@ -21,11 +19,12 @@ class _NoteState extends State<NoteWidget> with AutomaticKeepAliveClientMixin {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.all(8),
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Observer(builder: (context) {
|
||||
accountManager.sortedNotes;
|
||||
active.sortedNotes;
|
||||
return PaginatedDataTable(
|
||||
columns: [
|
||||
DataColumn(
|
||||
|
@ -39,20 +38,20 @@ class _NoteState extends State<NoteWidget> with AutomaticKeepAliveClientMixin {
|
|||
}),
|
||||
DataColumn(
|
||||
label: Text(S.of(context).datetime +
|
||||
accountManager.noteSortConfig.getIndicator("time")),
|
||||
active.noteSortConfig.getIndicator("time")),
|
||||
onSort: (_, __) {
|
||||
setState(() {
|
||||
accountManager.sortNotes("time");
|
||||
active.sortNotes("time");
|
||||
});
|
||||
},
|
||||
),
|
||||
DataColumn(
|
||||
numeric: true,
|
||||
label: Text(S.of(context).amount +
|
||||
accountManager.noteSortConfig.getIndicator("amount")),
|
||||
active.noteSortConfig.getIndicator("amount")),
|
||||
onSort: (_, __) {
|
||||
setState(() {
|
||||
accountManager.sortNotes("amount");
|
||||
active.sortNotes("amount");
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -76,11 +75,11 @@ class _NoteState extends State<NoteWidget> with AutomaticKeepAliveClientMixin {
|
|||
}
|
||||
|
||||
_onRowSelected(Note note) {
|
||||
accountManager.excludeNote(note);
|
||||
active.excludeNote(note);
|
||||
}
|
||||
|
||||
_selectInverse() {
|
||||
accountManager.invertExcludedNotes();
|
||||
active.invertExcludedNotes();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +91,7 @@ class NotesDataSource extends DataTableSource {
|
|||
|
||||
@override
|
||||
DataRow getRow(int index) {
|
||||
final note = accountManager.sortedNotes[index];
|
||||
final note = active.sortedNotes[index];
|
||||
final theme = Theme.of(context);
|
||||
final confsOrHeight = settings.showConfirmations
|
||||
? syncStatus.latestHeight - note.height + 1
|
||||
|
@ -127,7 +126,7 @@ class NotesDataSource extends DataTableSource {
|
|||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get rowCount => accountManager.notes.length;
|
||||
int get rowCount => active.notes.length;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => 0;
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:warp/dualmoneyinput.dart';
|
||||
import 'dualmoneyinput.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
|
||||
import 'main.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
class PaymentURIPage extends StatefulWidget {
|
||||
final String address;
|
||||
|
||||
PaymentURIPage(String? _address): address = _address ?? accountManager.active.address;
|
||||
PaymentURIPage(String? _address): address = _address ?? active.account.address;
|
||||
|
||||
@override
|
||||
PaymentURIState createState() => PaymentURIState();
|
||||
|
@ -27,7 +28,7 @@ class PaymentURIState extends State<PaymentURIPage> {
|
|||
super.initState();
|
||||
qrText = widget.address;
|
||||
Future.microtask(() {
|
||||
priceStore.fetchZecPrice();
|
||||
priceStore.fetchCoinPrice(active.coin);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -38,7 +39,7 @@ class PaymentURIState extends State<PaymentURIPage> {
|
|||
return Form(
|
||||
key: _formKey,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(title: Text(S.of(context).receive(coin.ticker))),
|
||||
appBar: AppBar(title: Text(S.of(context).receive(activeCoin().ticker))),
|
||||
body: SingleChildScrollView(
|
||||
child: GestureDetector(
|
||||
onTap: () { FocusScope.of(context).unfocus(); },
|
||||
|
@ -83,7 +84,7 @@ class PaymentURIState extends State<PaymentURIPage> {
|
|||
|
||||
final String _qrText;
|
||||
if (amount > 0) {
|
||||
_qrText = WarpApi.makePaymentURI(widget.address, amount, memo);
|
||||
_qrText = WarpApi.makePaymentURI(active.coin, widget.address, amount, memo);
|
||||
} else
|
||||
_qrText = widget.address;
|
||||
|
||||
|
|
|
@ -101,10 +101,10 @@ class FullBackupPage extends StatelessWidget {
|
|||
|
||||
_onSave(BuildContext context) async {
|
||||
Directory tempDir = await getTemporaryDirectory();
|
||||
String filename = "${tempDir.path}/${coin.app}.bak";
|
||||
String filename = "${tempDir.path}/$APP_NAME.bak";
|
||||
final file = File(filename);
|
||||
await file.writeAsString(backup);
|
||||
Share.shareFiles([filename], subject: S.of(context).encryptedBackup(coin.app));
|
||||
Share.shareFiles([filename], subject: S.of(context).encryptedBackup(APP_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ class _FullRestoreState extends State<FullRestorePage> {
|
|||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
||||
}
|
||||
else {
|
||||
await accountManager.refresh();
|
||||
await accounts.refresh();
|
||||
syncStatus.setAccountRestored(true);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
|
112
lib/restore.dart
112
lib/restore.dart
|
@ -1,9 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
import 'backup.dart';
|
||||
import 'main.dart';
|
||||
import 'generated/l10n.dart';
|
||||
|
||||
|
@ -16,16 +19,18 @@ class _RestorePageState extends State<RestorePage> {
|
|||
final _formKey = GlobalKey<FormState>();
|
||||
final _keyController = TextEditingController();
|
||||
final _nameController = TextEditingController();
|
||||
final _accountIndexController = TextEditingController(text: "0");
|
||||
final _shareController = TextEditingController();
|
||||
var _validKey = true;
|
||||
var _isVK = false;
|
||||
var _coin = 0;
|
||||
var _showIndex = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("${coin.symbol} Wallet"),
|
||||
title: Text(APP_NAME),
|
||||
),
|
||||
body: GestureDetector(
|
||||
onTap: () { FocusScope.of(context).unfocus(); },
|
||||
|
@ -34,15 +39,26 @@ class _RestorePageState extends State<RestorePage> {
|
|||
child: SingleChildScrollView(child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(children: [
|
||||
FormBuilderRadioGroup<int>(
|
||||
orientation: OptionsOrientation.horizontal,
|
||||
name: 'coin',
|
||||
initialValue: _coin,
|
||||
onChanged: (int? v) { _coin = v!; },
|
||||
options: [
|
||||
FormBuilderFieldOption(
|
||||
child: Text('Zcash'), value: 0),
|
||||
FormBuilderFieldOption(
|
||||
child: Text('Ycash'), value: 1),
|
||||
]),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: s.accountName),
|
||||
controller: _nameController,
|
||||
validator: (String? name) {
|
||||
if (name == null || name.isEmpty)
|
||||
return s.accountNameIsRequired;
|
||||
return null;
|
||||
},
|
||||
),
|
||||
decoration: InputDecoration(labelText: s.accountName),
|
||||
controller: _nameController,
|
||||
validator: (String? name) {
|
||||
if (name == null || name.isEmpty)
|
||||
return s.accountNameIsRequired;
|
||||
return null;
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -56,26 +72,39 @@ class _RestorePageState extends State<RestorePage> {
|
|||
controller: _keyController,
|
||||
onChanged: _checkKey,
|
||||
)),
|
||||
IconButton(
|
||||
icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScan)
|
||||
GestureDetector(onLongPress: _toggleShowAccountIndex,
|
||||
child: IconButton(
|
||||
icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScan))
|
||||
],
|
||||
),
|
||||
if (_isVK && coin.supportsMultisig) Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: s.secretShare,
|
||||
hintText: s.enterSecretShareIfAccountIsMultisignature),
|
||||
minLines: 4,
|
||||
maxLines: 4,
|
||||
controller: _shareController,
|
||||
// TODO: Check share
|
||||
)),
|
||||
IconButton(
|
||||
icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScanShare)
|
||||
],
|
||||
// if (_isVK && coin.supportsMultisig) Row(
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: TextFormField(
|
||||
// decoration: InputDecoration(
|
||||
// labelText: s.secretShare,
|
||||
// hintText: s.enterSecretShareIfAccountIsMultisignature),
|
||||
// minLines: 4,
|
||||
// maxLines: 4,
|
||||
// controller: _shareController,
|
||||
// // TODO: Check share
|
||||
// )),
|
||||
// IconButton(
|
||||
// icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScanShare)
|
||||
// ],
|
||||
// ),
|
||||
if (_showIndex) TextFormField(
|
||||
decoration: InputDecoration(labelText: s.accountIndex),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
|
||||
controller: _accountIndexController,
|
||||
validator: (String? name) {
|
||||
if (name == null || name.isEmpty)
|
||||
return s.accountNameIsRequired;
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
ButtonBar(children:
|
||||
confirmButtons(context, _validKey ? _onOK : null, okLabel: s.addnew, okIcon: Icon(Icons.add)))
|
||||
]))))));
|
||||
|
@ -83,9 +112,11 @@ class _RestorePageState extends State<RestorePage> {
|
|||
|
||||
_onOK() async {
|
||||
final s = S.of(context);
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final form = _formKey.currentState!;
|
||||
final accountIndex = int.parse(_accountIndexController.text);
|
||||
if (form.validate()) {
|
||||
final account =
|
||||
WarpApi.newAccount(_nameController.text, _keyController.text);
|
||||
WarpApi.newAccount(_coin, _nameController.text, _keyController.text, accountIndex);
|
||||
if (account < 0) {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
@ -103,27 +134,34 @@ class _RestorePageState extends State<RestorePage> {
|
|||
]));
|
||||
}
|
||||
else {
|
||||
if (_shareController.text.isNotEmpty)
|
||||
accountManager.storeShareSecret(account, _shareController.text);
|
||||
await accountManager.refresh();
|
||||
// if (_shareController.text.isNotEmpty)
|
||||
// accountManager.storeShareSecret(account, _shareController.text);
|
||||
// await accountManager.refresh();
|
||||
await accounts.refresh();
|
||||
if (_keyController.text != "") {
|
||||
syncStatus.setAccountRestored(true);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
else {
|
||||
if (accountManager.accounts.length == 1)
|
||||
WarpApi.skipToLastHeight(); // single new account -> quick sync
|
||||
Navigator.of(context).pushReplacementNamed('/backup', arguments: account);
|
||||
if (accounts.list.where((e) => e.coin == active.coin).length == 1)
|
||||
WarpApi.skipToLastHeight(0); // single new account -> quick sync
|
||||
Navigator.of(context).pushReplacementNamed('/backup', arguments: AccountId(_coin, account)); // Need coin type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_toggleShowAccountIndex() {
|
||||
setState(() {
|
||||
_showIndex = !_showIndex;
|
||||
});
|
||||
}
|
||||
|
||||
_checkKey(key) {
|
||||
setState(() {
|
||||
final keyType = WarpApi.validKey(key);
|
||||
final keyType = WarpApi.validKey(_coin, key);
|
||||
_validKey = key == "" || keyType >= 0;
|
||||
_isVK = keyType == 2;
|
||||
// _isVK = keyType == 2;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:warp/dualmoneyinput.dart';
|
||||
import 'dualmoneyinput.dart';
|
||||
import 'package:warp_api/types.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
|
@ -68,15 +68,13 @@ class SendState extends State<SendPage> {
|
|||
super.initState();
|
||||
|
||||
_newBlockAutorunDispose = autorun((_) async {
|
||||
final _ = syncStatus.latestHeight;
|
||||
final sBalance = await accountManager.getShieldedBalance();
|
||||
final tBalance = accountManager.tbalance;
|
||||
final excludedBalance = await accountManager.getExcludedBalance();
|
||||
final underConfirmedBalance =
|
||||
await accountManager.getUnderConfirmedBalance();
|
||||
final unconfirmedSpentBalance =
|
||||
await accountManager.getUnconfirmedSpentBalance();
|
||||
final unconfirmedBalance = accountManager.unconfirmedBalance;
|
||||
final _ = active.dataEpoch;
|
||||
final sBalance = active.balances.shieldedBalance;
|
||||
final tBalance = active.tbalance;
|
||||
final excludedBalance = active.balances.excludedBalance;
|
||||
final underConfirmedBalance = active.balances.underConfirmedBalance;
|
||||
final unconfirmedSpentBalance = active.balances.unconfirmedBalance;
|
||||
final unconfirmedBalance = active.balances.unconfirmedBalance;
|
||||
setState(() {
|
||||
_sBalance = sBalance;
|
||||
_tBalance = tBalance;
|
||||
|
@ -98,10 +96,10 @@ class SendState extends State<SendPage> {
|
|||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
final simpleMode = settings.simpleMode;
|
||||
_memoController.text = settings.memoSignature ?? s.sendFrom(coin.app);
|
||||
_memoController.text = settings.memoSignature ?? s.sendFrom(APP_NAME);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(s.sendCointicker(coin.ticker))),
|
||||
appBar: AppBar(title: Text(s.sendCointicker(active.coinDef.ticker))),
|
||||
body: GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
|
@ -117,7 +115,7 @@ class SendState extends State<SendPage> {
|
|||
textFieldConfiguration: TextFieldConfiguration(
|
||||
controller: _addressController,
|
||||
decoration: InputDecoration(
|
||||
labelText: s.sendCointickerTo(coin.ticker)),
|
||||
labelText: s.sendCointickerTo(active.coinDef.ticker)),
|
||||
minLines: 4,
|
||||
maxLines: 10,
|
||||
keyboardType: TextInputType.multiline,
|
||||
|
@ -172,7 +170,7 @@ class SendState extends State<SendPage> {
|
|||
title: Text(s.roundToMillis),
|
||||
value: _useMillis,
|
||||
onChanged: _setUseMillis),
|
||||
if (accountManager.canPay && !widget.isMulti)
|
||||
if (active.canPay && !widget.isMulti)
|
||||
CheckboxListTile(
|
||||
title: Text(s.useTransparentBalance),
|
||||
value: _useTransparent,
|
||||
|
@ -209,7 +207,7 @@ class SendState extends State<SendPage> {
|
|||
if (c.isNotEmpty) return null;
|
||||
final zaddr = WarpApi.getSaplingFromUA(v);
|
||||
if (zaddr.isNotEmpty) return null;
|
||||
if (!WarpApi.validAddress(v)) return s.invalidAddress;
|
||||
if (!WarpApi.validAddress(active.coin, v)) return s.invalidAddress;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -259,7 +257,7 @@ class SendState extends State<SendPage> {
|
|||
}
|
||||
|
||||
void _setPaymentURI(String uri) {
|
||||
final json = WarpApi.parsePaymentURI(uri);
|
||||
final json = WarpApi.parsePaymentURI(active.coin, uri);
|
||||
try {
|
||||
final payment = DecodedPaymentURI.fromJson(jsonDecode(json));
|
||||
setState(() {
|
||||
|
@ -302,7 +300,7 @@ class SendState extends State<SendPage> {
|
|||
title: Text(s.pleaseConfirm),
|
||||
content: SingleChildScrollView(
|
||||
child: Text(s.sendingAzecCointickerToAddress(
|
||||
aZEC, coin.ticker, _address))),
|
||||
aZEC, active.coinDef.ticker, _address))),
|
||||
actions: confirmButtons(
|
||||
context, () => Navigator.of(context).pop(true),
|
||||
okLabel: s.approve, cancelValue: false)));
|
||||
|
@ -424,9 +422,7 @@ Future<void> send(BuildContext context, List<Recipient> recipients, bool useTran
|
|||
final s = S.of(context);
|
||||
|
||||
String address = "";
|
||||
int amount = 0;
|
||||
for (var r in recipients) {
|
||||
amount += r.amount;
|
||||
if (address.isEmpty)
|
||||
address = r.address;
|
||||
else
|
||||
|
@ -439,9 +435,9 @@ Future<void> send(BuildContext context, List<Recipient> recipients, bool useTran
|
|||
if (settings.protectSend &&
|
||||
!await authenticate(context, s.pleaseAuthenticateToSend)) return;
|
||||
|
||||
if (accountManager.canPay) {
|
||||
if (active.canPay) {
|
||||
Navigator.of(context).pop();
|
||||
final tx = await WarpApi.sendPayment(accountManager.active.id, recipients,
|
||||
final tx = await WarpApi.sendPayment(active.coin, active.id, recipients,
|
||||
useTransparent, settings.anchorOffset, (progress) {
|
||||
progressPort.sendPort.send(progress);
|
||||
});
|
||||
|
@ -449,25 +445,20 @@ Future<void> send(BuildContext context, List<Recipient> recipients, bool useTran
|
|||
|
||||
final snackBar2 = SnackBar(content: Text("${s.txId}: $tx"));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
|
||||
await accountManager.fetchAccountData(true);
|
||||
await active.update();
|
||||
} else {
|
||||
Directory tempDir = await getTemporaryDirectory();
|
||||
String filename = "${tempDir.path}/tx.json";
|
||||
|
||||
final txjson = WarpApi.prepareTx(accountManager.active.id, recipients,
|
||||
final txjson = WarpApi.prepareTx(active.coin, active.id, recipients,
|
||||
useTransparent, settings.anchorOffset, filename);
|
||||
|
||||
if (coin.supportsMultisig && accountManager.active.share != null) {
|
||||
final txSummary = TxSummary(address, amount, txjson);
|
||||
Navigator.of(context).pushReplacementNamed('/multisign', arguments: txSummary);
|
||||
} else {
|
||||
final file = File(filename);
|
||||
await file.writeAsString(txjson);
|
||||
Share.shareFiles([filename], subject: s.unsignedTransactionFile);
|
||||
final file = File(filename);
|
||||
await file.writeAsString(txjson);
|
||||
Share.shareFiles([filename], subject: s.unsignedTransactionFile);
|
||||
|
||||
final snackBar2 = SnackBar(content: Text(s.fileSaved));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
final snackBar2 = SnackBar(content: Text(s.fileSaved));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'coin/coins.dart';
|
||||
|
||||
import 'coin/coin.dart';
|
||||
import 'main.dart';
|
||||
import 'generated/l10n.dart';
|
||||
|
||||
|
@ -12,7 +14,7 @@ class SettingsPage extends StatefulWidget {
|
|||
|
||||
final _settingsFormKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
class SettingsState extends State<SettingsPage> {
|
||||
class SettingsState extends State<SettingsPage> with SingleTickerProviderStateMixin {
|
||||
var _anchorController =
|
||||
TextEditingController(text: "${settings.anchorOffset}");
|
||||
var _thresholdController = TextEditingController(
|
||||
|
@ -20,27 +22,19 @@ class SettingsState extends State<SettingsPage> {
|
|||
var _memoController = TextEditingController();
|
||||
var _currency = settings.currency;
|
||||
var _needAuth = false;
|
||||
late TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
final simpleMode = settings.simpleMode;
|
||||
_memoController.text = settings.memoSignature ?? s.sendFrom(coin.app);
|
||||
|
||||
List<FormBuilderFieldOption> options = coin.lwd
|
||||
.map((lwd) => FormBuilderFieldOption<dynamic>(
|
||||
child: Text(lwd.name), value: lwd.name))
|
||||
.toList();
|
||||
options.add(
|
||||
FormBuilderFieldOption(
|
||||
value: 'custom',
|
||||
child: FormBuilderTextField(
|
||||
name: 'lwd_url',
|
||||
decoration: InputDecoration(labelText: s.custom),
|
||||
initialValue: settings.ldUrl,
|
||||
onSaved: _onURL,
|
||||
)),
|
||||
);
|
||||
_memoController.text = settings.memoSignature ?? s.sendFrom(APP_NAME);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(s.settings)),
|
||||
|
@ -64,14 +58,16 @@ class SettingsState extends State<SettingsPage> {
|
|||
FormBuilderFieldOption(
|
||||
child: Text(s.advanced), value: 'advanced'),
|
||||
]),
|
||||
if (!simpleMode) FormBuilderRadioGroup(
|
||||
orientation: OptionsOrientation.vertical,
|
||||
name: 'servers',
|
||||
decoration: InputDecoration(
|
||||
labelText: s.server),
|
||||
initialValue: settings.ldUrlChoice,
|
||||
onSaved: _onChoice,
|
||||
options: options),
|
||||
if (!simpleMode) TabBar(controller: _tabController, tabs: [Tab(text: "Zcash"), Tab(text: "Ycash")]),
|
||||
if (!simpleMode) SizedBox(height: 200, child: TabBarView(controller: _tabController, children: [ServerSelect(0), ServerSelect(1)])),
|
||||
// if (!simpleMode) FormBuilderRadioGroup(
|
||||
// orientation: OptionsOrientation.vertical,
|
||||
// name: 'servers',
|
||||
// decoration: InputDecoration(
|
||||
// labelText: s.server),
|
||||
// initialValue: settings.ldUrlChoice,
|
||||
// onSaved: _onChoice,
|
||||
// options: options),
|
||||
FormBuilderRadioGroup(
|
||||
orientation: OptionsOrientation.horizontal,
|
||||
name: 'themes',
|
||||
|
@ -138,13 +134,13 @@ class SettingsState extends State<SettingsPage> {
|
|||
_needAuth = true;
|
||||
},
|
||||
onSaved: _onProtectSend)),
|
||||
if (coin.supportsUA)
|
||||
Expanded(
|
||||
child: FormBuilderCheckbox(
|
||||
name: 'use_ua',
|
||||
title: Text(s.useUa),
|
||||
initialValue: settings.useUA,
|
||||
onSaved: _onUseUA)),
|
||||
// if (coin.supportsUA)
|
||||
// Expanded(
|
||||
// child: FormBuilderCheckbox(
|
||||
// name: 'use_ua',
|
||||
// title: Text(s.useUa),
|
||||
// initialValue: settings.useUA,
|
||||
// onSaved: _onUseUA)),
|
||||
]),
|
||||
if (!simpleMode) FormBuilderTextField(
|
||||
decoration: InputDecoration(
|
||||
|
@ -216,20 +212,9 @@ class SettingsState extends State<SettingsPage> {
|
|||
}
|
||||
|
||||
_onMode(v) {
|
||||
final simpleMode = v == 'simple';
|
||||
if (settings.simpleMode != simpleMode)
|
||||
showSnackBar(S.of(context).changingTheModeWillTakeEffectAtNextRestart);
|
||||
settings.setMode(v == 'simple');
|
||||
}
|
||||
|
||||
_onChoice(v) {
|
||||
settings.setURLChoice(v);
|
||||
}
|
||||
|
||||
_onURL(v) {
|
||||
settings.setURL(v);
|
||||
}
|
||||
|
||||
_onTheme(v) {
|
||||
settings.setTheme(v);
|
||||
}
|
||||
|
@ -268,6 +253,7 @@ class SettingsState extends State<SettingsPage> {
|
|||
if (form.validate()) {
|
||||
if (_needAuth && !await authenticate(context, S.of(context).protectSendSettingChanged)) return;
|
||||
form.save();
|
||||
settings.updateLWD();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
@ -288,3 +274,75 @@ class SettingsState extends State<SettingsPage> {
|
|||
Navigator.of(context).pushNamed('/edit_theme');
|
||||
}
|
||||
}
|
||||
|
||||
class ServerSelect extends StatefulWidget {
|
||||
final int coin;
|
||||
|
||||
ServerSelect(this.coin);
|
||||
_ServerSelectState createState() => _ServerSelectState(coin);
|
||||
}
|
||||
|
||||
class _ServerSelectState extends State<ServerSelect> with
|
||||
AutomaticKeepAliveClientMixin {
|
||||
final int coin;
|
||||
late String choice;
|
||||
late String customUrl;
|
||||
bool _saved = true;
|
||||
|
||||
_ServerSelectState(this.coin) {
|
||||
choice = settings.servers[coin].choice;
|
||||
customUrl = settings.servers[coin].customUrl;
|
||||
}
|
||||
|
||||
CoinBase get coinDef => settings.coins[widget.coin].def;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final s = S.of(context);
|
||||
List<FormBuilderFieldOption<String>> options = coinDef.lwd
|
||||
.map((lwd) => FormBuilderFieldOption<String>(
|
||||
child: Text(lwd.name), value: lwd.name))
|
||||
.toList();
|
||||
options.add(
|
||||
FormBuilderFieldOption(
|
||||
value: 'custom',
|
||||
child: FormBuilderTextField(
|
||||
name: 'lwd_url ${coinDef.ticker}',
|
||||
decoration: InputDecoration(labelText: s.custom),
|
||||
initialValue: customUrl,
|
||||
onSaved: _save,
|
||||
onChanged: (v) {
|
||||
if (v == null) return;
|
||||
customUrl = v;
|
||||
_saved = false;
|
||||
},
|
||||
)),
|
||||
);
|
||||
|
||||
return FormBuilderRadioGroup<String>(
|
||||
orientation: OptionsOrientation.vertical,
|
||||
name: 'lwd ${coinDef.ticker}',
|
||||
decoration: InputDecoration(
|
||||
labelText: s.server),
|
||||
initialValue: choice,
|
||||
onSaved: _save,
|
||||
onChanged: (v) {
|
||||
if (v == null) return;
|
||||
choice = v;
|
||||
_saved = false;
|
||||
},
|
||||
options: options);
|
||||
}
|
||||
|
||||
void _save(_) async {
|
||||
if (_saved) return;
|
||||
await settings.servers[coin].savePrefs(choice, customUrl);
|
||||
_saved = true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
CoinBase activeCoin() => settings.coins[active.coin].def;
|
||||
|
|
904
lib/store.dart
904
lib/store.dart
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
import 'package:warp/main.dart';
|
||||
import 'main.dart';
|
||||
|
||||
import 'generated/l10n.dart';
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'main.dart';
|
||||
import 'settings.dart';
|
||||
import 'store.dart';
|
||||
import 'generated/l10n.dart';
|
||||
|
||||
|
@ -41,6 +42,6 @@ class TransactionState extends State<TransactionPage> {
|
|||
}
|
||||
|
||||
_onOpen() {
|
||||
launch("${coin.explorerUrl}${widget.tx.fullTxId}");
|
||||
launch("${activeCoin().explorerUrl}${widget.tx.fullTxId}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#ifndef __APPLE__
|
||||
typedef char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef short int uint16_t;
|
||||
typedef long long int int64_t;
|
||||
typedef long long int uint64_t;
|
||||
|
@ -10,76 +11,78 @@ typedef char bool;
|
|||
#endif
|
||||
typedef void *DartPostCObjectFnType;
|
||||
|
||||
void init_wallet(char *db_path, char *ld_url);
|
||||
void init_wallet(char *db_path);
|
||||
|
||||
void reset_app(void);
|
||||
|
||||
void warp_sync(bool get_tx, uint32_t anchor_offset, int64_t port);
|
||||
int8_t warp_sync(uint8_t coin, bool get_tx, uint32_t anchor_offset, int64_t port);
|
||||
|
||||
void dart_post_cobject(DartPostCObjectFnType ptr);
|
||||
|
||||
uint32_t get_latest_height(void);
|
||||
uint32_t get_latest_height(uint8_t coin);
|
||||
|
||||
int8_t is_valid_key(char *seed);
|
||||
int8_t is_valid_key(uint8_t coin, char *seed);
|
||||
|
||||
bool valid_address(char *address);
|
||||
bool valid_address(uint8_t coin, char *address);
|
||||
|
||||
char *new_address(uint32_t account);
|
||||
char *new_address(uint8_t coin, uint32_t account);
|
||||
|
||||
void set_mempool_account(uint32_t account);
|
||||
void set_mempool_account(uint8_t coin, uint32_t account);
|
||||
|
||||
int32_t new_account(char *name, char *data);
|
||||
int32_t new_account(uint8_t coin, char *name, char *data, uint32_t index);
|
||||
|
||||
int64_t get_mempool_balance(void);
|
||||
int32_t new_sub_account(uint8_t coin, uint32_t id, char *name);
|
||||
|
||||
const char *send_multi_payment(uint32_t account,
|
||||
int64_t get_mempool_balance(uint8_t coin);
|
||||
|
||||
const char *send_multi_payment(uint8_t coin,
|
||||
uint32_t account,
|
||||
char *recipients_json,
|
||||
uint32_t anchor_offset,
|
||||
bool use_transparent,
|
||||
int64_t port);
|
||||
|
||||
int8_t try_warp_sync(bool get_tx, uint32_t anchor_offset);
|
||||
void skip_to_last_height(uint8_t coin);
|
||||
|
||||
void skip_to_last_height(void);
|
||||
void rewind_to_height(uint8_t coin, uint32_t height);
|
||||
|
||||
void rewind_to_height(uint32_t height);
|
||||
int64_t mempool_sync(uint8_t coin);
|
||||
|
||||
int64_t mempool_sync(void);
|
||||
void mempool_reset(uint8_t coin, uint32_t height);
|
||||
|
||||
void mempool_reset(uint32_t height);
|
||||
uint64_t get_taddr_balance(uint8_t coin, uint32_t account);
|
||||
|
||||
uint64_t get_taddr_balance(uint32_t account);
|
||||
char *shield_taddr(uint8_t coin, uint32_t account);
|
||||
|
||||
char *shield_taddr(uint32_t account);
|
||||
void set_lwd_url(uint8_t coin, char *url);
|
||||
|
||||
void set_lwd_url(char *url);
|
||||
|
||||
char *prepare_multi_payment(uint32_t account,
|
||||
char *prepare_multi_payment(uint8_t coin,
|
||||
uint32_t account,
|
||||
char *recipients_json,
|
||||
bool use_transparent,
|
||||
uint32_t anchor_offset);
|
||||
|
||||
char *broadcast(char *tx_filename);
|
||||
char *broadcast(uint8_t coin, char *tx_filename);
|
||||
|
||||
char *broadcast_txhex(char *txhex);
|
||||
char *broadcast_txhex(uint8_t coin, char *txhex);
|
||||
|
||||
uint32_t sync_historical_prices(int64_t now, uint32_t days, char *currency);
|
||||
uint32_t sync_historical_prices(uint8_t coin, int64_t now, uint32_t days, char *currency);
|
||||
|
||||
char *get_ua(char *sapling_addr, char *transparent_addr);
|
||||
|
||||
char *get_sapling(char *ua_addr);
|
||||
|
||||
void store_contact(uint32_t id, char *name, char *address, bool dirty);
|
||||
void store_contact(uint8_t coin, uint32_t id, char *name, char *address, bool dirty);
|
||||
|
||||
char *commit_unsaved_contacts(uint32_t account, uint32_t anchor_offset);
|
||||
char *commit_unsaved_contacts(uint8_t coin, uint32_t account, uint32_t anchor_offset);
|
||||
|
||||
void delete_account(uint32_t account);
|
||||
void delete_account(uint8_t coin, uint32_t account);
|
||||
|
||||
void truncate_data(void);
|
||||
void truncate_data(uint8_t coin);
|
||||
|
||||
char *make_payment_uri(char *address, uint64_t amount, char *memo);
|
||||
char *make_payment_uri(uint8_t coin, char *address, uint64_t amount, char *memo);
|
||||
|
||||
char *parse_payment_uri(char *uri);
|
||||
char *parse_payment_uri(uint8_t coin, char *uri);
|
||||
|
||||
char *generate_random_enc_key(void);
|
||||
|
||||
|
@ -87,9 +90,9 @@ char *get_full_backup(char *key);
|
|||
|
||||
char *restore_full_backup(char *key, char *backup);
|
||||
|
||||
void store_share_secret(uint32_t account, char *secret);
|
||||
void store_share_secret(uint8_t coin, uint32_t account, char *secret);
|
||||
|
||||
char *get_share_secret(uint32_t account);
|
||||
char *get_share_secret(uint8_t coin, uint32_t account);
|
||||
|
||||
void run_aggregator(char *secret_share, uint16_t port, int64_t send_port);
|
||||
|
||||
|
@ -104,6 +107,6 @@ uint32_t run_multi_signer(char *address,
|
|||
char *my_url,
|
||||
uint16_t port);
|
||||
|
||||
char *split_account(uint32_t threshold, uint32_t participants, uint32_t account);
|
||||
char *split_account(uint8_t coin, uint32_t threshold, uint32_t participants, uint32_t account);
|
||||
|
||||
void dummy_export(void);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#ifndef __APPLE__
|
||||
typedef char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef short int uint16_t;
|
||||
typedef long long int int64_t;
|
||||
typedef long long int uint64_t;
|
||||
|
|
|
@ -6,7 +6,10 @@ use once_cell::sync::OnceCell;
|
|||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use sync::{broadcast_tx, ChainError, MemPool, Wallet};
|
||||
use sync::{
|
||||
broadcast_tx, decrypt_backup, encrypt_backup, get_coin_type, ChainError, CoinType, KeyHelpers,
|
||||
MemPool, Wallet,
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::time::Duration;
|
||||
use zcash_multisig::{
|
||||
|
@ -16,8 +19,10 @@ use zcash_multisig::{
|
|||
use zcash_primitives::transaction::builder::Progress;
|
||||
|
||||
static RUNTIME: OnceCell<Mutex<Runtime>> = OnceCell::new();
|
||||
static WALLET: OnceCell<Mutex<Wallet>> = OnceCell::new();
|
||||
static MEMPOOL: OnceCell<Mutex<MemPool>> = OnceCell::new();
|
||||
static YWALLET: OnceCell<Mutex<Wallet>> = OnceCell::new();
|
||||
static ZWALLET: OnceCell<Mutex<Wallet>> = OnceCell::new();
|
||||
static YMEMPOOL: OnceCell<Mutex<MemPool>> = OnceCell::new();
|
||||
static ZMEMPOOL: OnceCell<Mutex<MemPool>> = OnceCell::new();
|
||||
static SYNCLOCK: OnceCell<Mutex<()>> = OnceCell::new();
|
||||
static MULTISIG_AGG_LOCK: OnceCell<Mutex<MultisigAggregator>> = OnceCell::new();
|
||||
static MULTISIG_SIGN_LOCK: OnceCell<Mutex<MultisigClient>> = OnceCell::new();
|
||||
|
@ -53,18 +58,28 @@ fn log_result_string(result: anyhow::Result<String>) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn init_wallet(db_path: &str, ld_url: &str) {
|
||||
pub fn init_wallet(db_path: &str) {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Info));
|
||||
info!("Init");
|
||||
RUNTIME.get_or_init(|| Mutex::new(Runtime::new().unwrap()));
|
||||
WALLET.get_or_init(|| {
|
||||
info!("Wallet Init");
|
||||
let wallet = Wallet::new(db_path, ld_url);
|
||||
YWALLET.get_or_init(|| {
|
||||
info!("YWallet Init");
|
||||
let wallet = Wallet::new(CoinType::Ycash, &format!("{}/yec.db", db_path));
|
||||
Mutex::new(wallet)
|
||||
});
|
||||
MEMPOOL.get_or_init(|| {
|
||||
info!("Mempool Init");
|
||||
let mempool = MemPool::new(db_path, ld_url);
|
||||
ZWALLET.get_or_init(|| {
|
||||
info!("ZWallet Init");
|
||||
let wallet = Wallet::new(CoinType::Zcash, &format!("{}/zec.db", db_path));
|
||||
Mutex::new(wallet)
|
||||
});
|
||||
YMEMPOOL.get_or_init(|| {
|
||||
info!("YMempool Init");
|
||||
let mempool = MemPool::new(CoinType::Ycash, &format!("{}/yec.db", db_path));
|
||||
Mutex::new(mempool)
|
||||
});
|
||||
ZMEMPOOL.get_or_init(|| {
|
||||
info!("ZMempool Init");
|
||||
let mempool = MemPool::new(CoinType::Zcash, &format!("{}/zec.db", db_path));
|
||||
Mutex::new(mempool)
|
||||
});
|
||||
SYNCLOCK.get_or_init(|| Mutex::new(()));
|
||||
|
@ -74,21 +89,48 @@ pub fn init_wallet(db_path: &str, ld_url: &str) {
|
|||
|
||||
pub fn reset_app() {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
wallet.reset_db()
|
||||
let wallet = get_lock(&YWALLET)?;
|
||||
wallet.reset_db()?;
|
||||
let wallet = get_lock(&ZWALLET)?;
|
||||
wallet.reset_db()?;
|
||||
Ok(())
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn new_account(name: &str, data: &str) -> i32 {
|
||||
pub fn new_account(coin: u8, name: &str, data: &str, index: u32) -> i32 {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
wallet.new_account(name, data)
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.new_account(name, data, index)
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn new_sub_account(coin: u8, id: u32, name: &str) -> i32 {
|
||||
let res = || {
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let id = wallet.new_sub_account(id, name)?;
|
||||
Ok(id)
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
fn get_wallet_lock(coin: u8) -> anyhow::Result<MutexGuard<'static, Wallet>> {
|
||||
match coin {
|
||||
1 => get_lock(&YWALLET),
|
||||
_ => get_lock(&ZWALLET),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mempool_lock(coin: u8) -> anyhow::Result<MutexGuard<'static, MemPool>> {
|
||||
match coin {
|
||||
1 => get_lock(&YMEMPOOL),
|
||||
_ => get_lock(&ZMEMPOOL),
|
||||
}
|
||||
}
|
||||
|
||||
async fn warp(
|
||||
coin: u8,
|
||||
get_tx: bool,
|
||||
anchor_offset: u32,
|
||||
db_path: &str,
|
||||
|
@ -96,7 +138,9 @@ async fn warp(
|
|||
port: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
info!("Sync started");
|
||||
let coin_type = get_coin_type(coin);
|
||||
Wallet::sync_ex(
|
||||
coin_type,
|
||||
get_tx,
|
||||
anchor_offset,
|
||||
&db_path,
|
||||
|
@ -114,7 +158,7 @@ async fn warp(
|
|||
)
|
||||
.await?;
|
||||
info!("Sync finished");
|
||||
let mut mempool = get_lock(&MEMPOOL)?;
|
||||
let mut mempool = get_mempool_lock(coin)?;
|
||||
mempool.scan().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -136,60 +180,68 @@ fn convert_sync_result(result: anyhow::Result<()>) -> i8 {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn warp_sync(get_tx: bool, anchor_offset: u32, port: i64) -> i8 {
|
||||
pub fn warp_sync(coin: u8, get_tx: bool, anchor_offset: u32, port: i64) -> i8 {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Info));
|
||||
let _sync_lock = get_lock(&SYNCLOCK)?;
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let db_path = wallet.db_path.clone();
|
||||
let ld_url = wallet.ld_url.clone();
|
||||
drop(wallet);
|
||||
warp(get_tx, anchor_offset, &db_path, &ld_url, port).await?;
|
||||
warp(coin, get_tx, anchor_offset, &db_path, &ld_url, port).await?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
});
|
||||
convert_sync_result(res)
|
||||
}
|
||||
|
||||
pub fn try_warp_sync(get_tx: bool, anchor_offset: u32) -> i8 {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Info));
|
||||
let _sync_lock = SYNCLOCK
|
||||
.get()
|
||||
.ok_or_else(|| anyhow::anyhow!("Lock not initialized"))?
|
||||
.try_lock();
|
||||
if _sync_lock.is_ok() {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let db_path = wallet.db_path.clone();
|
||||
let ld_url = wallet.ld_url.clone();
|
||||
drop(wallet);
|
||||
warp(get_tx, anchor_offset, &db_path, &ld_url, 0).await?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!(ChainError::Busy))
|
||||
}
|
||||
});
|
||||
convert_sync_result(res)
|
||||
// pub fn try_warp_sync(coin: u8, get_tx: bool, anchor_offset: u32) -> i8 {
|
||||
// let r = get_runtime();
|
||||
// let res = r.block_on(async {
|
||||
// android_logger::init_once(Config::default().with_min_level(Level::Info));
|
||||
// let _sync_lock = SYNCLOCK
|
||||
// .get()
|
||||
// .ok_or_else(|| anyhow::anyhow!("Lock not initialized"))?
|
||||
// .try_lock();
|
||||
// if _sync_lock.is_ok() {
|
||||
// let wallet = get_wallet_lock(coin)?;
|
||||
// let db_path = wallet.db_path.clone();
|
||||
// let ld_url = wallet.ld_url.clone();
|
||||
// drop(wallet);
|
||||
// warp(coin, get_tx, anchor_offset, &db_path, &ld_url, 0).await?;
|
||||
// Ok::<_, anyhow::Error>(())
|
||||
// } else {
|
||||
// Err(anyhow::anyhow!(ChainError::Busy))
|
||||
// }
|
||||
// });
|
||||
// convert_sync_result(res)
|
||||
// }
|
||||
|
||||
pub fn is_valid_key(coin: u8, seed: &str) -> i8 {
|
||||
let coin_type = get_coin_type(coin);
|
||||
let kh = KeyHelpers::new(coin_type);
|
||||
kh.is_valid_key(seed)
|
||||
}
|
||||
|
||||
pub fn valid_address(address: &str) -> bool {
|
||||
Wallet::valid_address(address)
|
||||
pub fn valid_address(coin: u8, address: &str) -> bool {
|
||||
let coin_type = get_coin_type(coin);
|
||||
let kh = KeyHelpers::new(coin_type);
|
||||
kh.valid_address(address)
|
||||
}
|
||||
|
||||
pub fn new_address(account: u32) -> String {
|
||||
pub fn new_address(coin: u8, account: u32) -> String {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.new_diversified_address(account)
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn get_latest_height() -> u32 {
|
||||
pub fn get_latest_height(coin: u8) -> u32 {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Info));
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let height = sync::latest_height(&wallet.ld_url).await?;
|
||||
Ok::<_, anyhow::Error>(height)
|
||||
});
|
||||
|
@ -212,6 +264,7 @@ fn report_progress(progress: Progress, port: i64) {
|
|||
}
|
||||
|
||||
pub fn send_multi_payment(
|
||||
coin: u8,
|
||||
account: u32,
|
||||
recipients_json: &str,
|
||||
use_transparent: bool,
|
||||
|
@ -220,7 +273,7 @@ pub fn send_multi_payment(
|
|||
) -> String {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let mut wallet = get_lock(&WALLET)?;
|
||||
let mut wallet = get_wallet_lock(coin)?;
|
||||
let height = sync::latest_height(&wallet.ld_url).await?;
|
||||
let recipients = Wallet::parse_recipients(recipients_json)?;
|
||||
let res = wallet
|
||||
|
@ -240,84 +293,88 @@ pub fn send_multi_payment(
|
|||
log_result_string(res)
|
||||
}
|
||||
|
||||
pub fn skip_to_last_height() {
|
||||
pub fn skip_to_last_height(coin: u8) {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.skip_to_last_height().await
|
||||
});
|
||||
log_result(res)
|
||||
}
|
||||
|
||||
pub fn rewind_to_height(height: u32) {
|
||||
pub fn rewind_to_height(coin: u8, height: u32) {
|
||||
let res = || {
|
||||
let mut wallet = get_lock(&WALLET)?;
|
||||
let mut wallet = get_wallet_lock(coin)?;
|
||||
wallet.rewind_to_height(height)
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn mempool_sync() -> i64 {
|
||||
pub fn mempool_sync(coin: u8) -> i64 {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let mut mempool = get_lock(&MEMPOOL)?;
|
||||
let mut mempool = get_mempool_lock(coin)?;
|
||||
mempool.scan().await
|
||||
});
|
||||
log_result(res)
|
||||
}
|
||||
|
||||
pub fn set_mempool_account(account: u32) {
|
||||
pub fn set_mempool_account(coin: u8, account: u32) {
|
||||
let res = || {
|
||||
let mut mempool = get_lock(&MEMPOOL)?;
|
||||
let mut mempool = get_mempool_lock(coin)?;
|
||||
mempool.set_account(account)
|
||||
};
|
||||
log_result(res());
|
||||
}
|
||||
|
||||
pub fn mempool_reset(height: u32) {
|
||||
pub fn mempool_reset(coin: u8, height: u32) {
|
||||
let res = || {
|
||||
let mut mempool = get_lock(&MEMPOOL)?;
|
||||
let mut mempool = get_mempool_lock(coin)?;
|
||||
mempool.clear(height)
|
||||
};
|
||||
log_result(res());
|
||||
}
|
||||
|
||||
pub fn get_mempool_balance() -> i64 {
|
||||
pub fn get_mempool_balance(coin: u8) -> i64 {
|
||||
let res = || {
|
||||
let mempool = get_lock(&MEMPOOL)?;
|
||||
let mempool = get_mempool_lock(coin)?;
|
||||
Ok(mempool.get_unconfirmed_balance())
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn get_taddr_balance(account: u32) -> u64 {
|
||||
pub fn get_taddr_balance(coin: u8, account: u32) -> u64 {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.get_taddr_balance(account).await
|
||||
});
|
||||
log_result(res)
|
||||
}
|
||||
|
||||
pub fn shield_taddr(account: u32) -> String {
|
||||
pub fn shield_taddr(coin: u8, account: u32) -> String {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let mut wallet = get_lock(&WALLET)?;
|
||||
let mut wallet = get_wallet_lock(coin)?;
|
||||
let height = sync::latest_height(&wallet.ld_url).await?;
|
||||
wallet.shield_taddr(account, height).await
|
||||
});
|
||||
log_result(res)
|
||||
}
|
||||
|
||||
pub fn set_lwd_url(url: &str) {
|
||||
pub fn set_lwd_url(coin: u8, url: &str) {
|
||||
let res = || {
|
||||
let mut wallet = get_lock(&WALLET)?;
|
||||
wallet.set_lwd_url(url)
|
||||
let mut wallet = get_wallet_lock(coin)?;
|
||||
wallet.set_lwd_url(url)?;
|
||||
let mut mempool = get_mempool_lock(coin)?;
|
||||
mempool.set_lwd_url(url)?;
|
||||
Ok(())
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn prepare_multi_payment(
|
||||
coin: u8,
|
||||
account: u32,
|
||||
recipients_json: &str,
|
||||
use_transparent: bool,
|
||||
|
@ -325,7 +382,7 @@ pub fn prepare_multi_payment(
|
|||
) -> String {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let mut wallet = get_lock(&WALLET)?;
|
||||
let mut wallet = get_wallet_lock(coin)?;
|
||||
let last_height = sync::latest_height(&wallet.ld_url).await?;
|
||||
let recipients = Wallet::parse_recipients(recipients_json)?;
|
||||
let tx = wallet
|
||||
|
@ -342,10 +399,10 @@ pub fn prepare_multi_payment(
|
|||
log_result_string(res)
|
||||
}
|
||||
|
||||
pub fn broadcast(tx_filename: &str) -> String {
|
||||
pub fn broadcast(coin: u8, tx_filename: &str) -> String {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let mut file = File::open(&tx_filename)?;
|
||||
let mut s = String::new();
|
||||
file.read_to_string(&mut s)?;
|
||||
|
@ -355,20 +412,20 @@ pub fn broadcast(tx_filename: &str) -> String {
|
|||
log_result_string(res)
|
||||
}
|
||||
|
||||
pub fn broadcast_txhex(txhex: &str) -> String {
|
||||
pub fn broadcast_txhex(coin: u8, txhex: &str) -> String {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let tx = hex::decode(txhex)?;
|
||||
broadcast_tx(&tx, &wallet.ld_url).await
|
||||
});
|
||||
log_result_string(res)
|
||||
}
|
||||
|
||||
pub fn sync_historical_prices(now: i64, days: u32, currency: &str) -> u32 {
|
||||
pub fn sync_historical_prices(coin: u8, now: i64, days: u32, currency: &str) -> u32 {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let mut wallet = get_lock(&WALLET)?;
|
||||
let mut wallet = get_wallet_lock(coin)?;
|
||||
wallet.sync_historical_prices(now, days, currency).await
|
||||
});
|
||||
log_result(res)
|
||||
|
@ -390,53 +447,55 @@ pub fn get_sapling(ua_addr: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn store_contact(id: u32, name: &str, address: &str, dirty: bool) {
|
||||
pub fn store_contact(coin: u8, id: u32, name: &str, address: &str, dirty: bool) {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.store_contact(id, name, address, dirty)?;
|
||||
Ok(())
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn commit_unsaved_contacts(account: u32, anchor_offset: u32) -> String {
|
||||
pub fn commit_unsaved_contacts(coin: u8, account: u32, anchor_offset: u32) -> String {
|
||||
let r = get_runtime();
|
||||
let res = r.block_on(async {
|
||||
let mut wallet = get_lock(&WALLET)?;
|
||||
let mut wallet = get_wallet_lock(coin)?;
|
||||
wallet.commit_unsaved_contacts(account, anchor_offset).await
|
||||
});
|
||||
log_result_string(res)
|
||||
}
|
||||
|
||||
pub fn truncate_data() {
|
||||
pub fn truncate_data(coin: u8) {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.truncate_data()?;
|
||||
Ok(())
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn delete_account(account: u32) {
|
||||
pub fn delete_account(coin: u8, account: u32) {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.delete_account(account)?;
|
||||
Ok(())
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn make_payment_uri(address: &str, amount: u64, memo: &str) -> String {
|
||||
pub fn make_payment_uri(coin: u8, address: &str, amount: u64, memo: &str) -> String {
|
||||
let res = || {
|
||||
let uri = Wallet::make_payment_uri(address, amount, memo)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let uri = wallet.make_payment_uri(address, amount, memo)?;
|
||||
Ok(uri)
|
||||
};
|
||||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn parse_payment_uri(uri: &str) -> String {
|
||||
pub fn parse_payment_uri(coin: u8, uri: &str) -> String {
|
||||
let res = || {
|
||||
let payment_json = Wallet::parse_payment_uri(uri)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let payment_json = wallet.parse_payment_uri(uri)?;
|
||||
Ok(payment_json)
|
||||
};
|
||||
log_result(res())
|
||||
|
@ -448,8 +507,13 @@ pub fn generate_random_enc_key() -> String {
|
|||
|
||||
pub fn get_full_backup(key: &str) -> String {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let backup = wallet.get_full_backup(key)?;
|
||||
let mut accounts = vec![];
|
||||
for coin in [0, 1] {
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
accounts.extend(wallet.get_full_backup()?);
|
||||
}
|
||||
|
||||
let backup = encrypt_backup(&accounts, key)?;
|
||||
Ok(backup)
|
||||
};
|
||||
log_result(res())
|
||||
|
@ -457,16 +521,19 @@ pub fn get_full_backup(key: &str) -> String {
|
|||
|
||||
pub fn restore_full_backup(key: &str, backup: &str) -> String {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
wallet.restore_full_backup(key, backup)?;
|
||||
let accounts = decrypt_backup(key, backup)?;
|
||||
for coin in [0, 1] {
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
wallet.restore_full_backup(&accounts)?;
|
||||
}
|
||||
Ok(String::new())
|
||||
};
|
||||
log_result_string(res())
|
||||
}
|
||||
|
||||
pub fn store_share_secret(account: u32, secret: &str) {
|
||||
pub fn store_share_secret(coin: u8, account: u32, secret: &str) {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let share = SecretShare::decode(secret)?;
|
||||
wallet.store_share_secret(
|
||||
account,
|
||||
|
@ -480,9 +547,9 @@ pub fn store_share_secret(account: u32, secret: &str) {
|
|||
log_result(res())
|
||||
}
|
||||
|
||||
pub fn get_share_secret(account: u32) -> String {
|
||||
pub fn get_share_secret(coin: u8, account: u32) -> String {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let secret = wallet.get_share_secret(account)?;
|
||||
Ok(secret)
|
||||
};
|
||||
|
@ -583,9 +650,9 @@ pub fn run_multi_signer(
|
|||
log_result(res)
|
||||
}
|
||||
|
||||
pub fn split_account(threshold: u32, participants: u32, account: u32) -> String {
|
||||
pub fn split_account(coin: u8, threshold: u32, participants: u32, account: u32) -> String {
|
||||
let res = || {
|
||||
let wallet = get_lock(&WALLET)?;
|
||||
let wallet = get_wallet_lock(coin)?;
|
||||
let sk = wallet.get_sk(account)?;
|
||||
let shares = zcash_multisig::split_account(threshold as usize, participants as usize, &sk)?;
|
||||
Ok(shares)
|
||||
|
|
|
@ -7,10 +7,9 @@ mod api;
|
|||
static mut POST_COBJ: Option<ffi::DartPostCObjectFnType> = None;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn init_wallet(db_path: *mut c_char, ld_url: *mut c_char) {
|
||||
pub unsafe extern "C" fn init_wallet(db_path: *mut c_char) {
|
||||
let db_path = CStr::from_ptr(db_path).to_string_lossy();
|
||||
let ld_url = CStr::from_ptr(ld_url).to_string_lossy();
|
||||
api::init_wallet(&db_path, &ld_url);
|
||||
api::init_wallet(&db_path);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -19,8 +18,8 @@ pub unsafe extern "C" fn reset_app() {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn warp_sync(get_tx: bool, anchor_offset: u32, port: i64) {
|
||||
api::warp_sync(get_tx, anchor_offset, port);
|
||||
pub unsafe extern "C" fn warp_sync(coin: u8, get_tx: bool, anchor_offset: u32, port: i64) -> i8 {
|
||||
api::warp_sync(coin, get_tx, anchor_offset, port)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -29,47 +28,59 @@ pub unsafe extern "C" fn dart_post_cobject(ptr: ffi::DartPostCObjectFnType) {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_latest_height() -> u32 {
|
||||
api::get_latest_height()
|
||||
pub unsafe extern "C" fn get_latest_height(coin: u8) -> u32 {
|
||||
api::get_latest_height(coin)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn is_valid_key(seed: *mut c_char) -> i8 {
|
||||
pub unsafe extern "C" fn is_valid_key(coin: u8, seed: *mut c_char) -> i8 {
|
||||
let seed = CStr::from_ptr(seed).to_string_lossy();
|
||||
sync::is_valid_key(&seed)
|
||||
api::is_valid_key(coin, &seed)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn valid_address(address: *mut c_char) -> bool {
|
||||
pub unsafe extern "C" fn valid_address(coin: u8, address: *mut c_char) -> bool {
|
||||
let address = CStr::from_ptr(address).to_string_lossy();
|
||||
api::valid_address(&address)
|
||||
api::valid_address(coin, &address)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn new_address(account: u32) -> *mut c_char {
|
||||
let address = api::new_address(account);
|
||||
pub unsafe extern "C" fn new_address(coin: u8, account: u32) -> *mut c_char {
|
||||
let address = api::new_address(coin, account);
|
||||
CString::new(address).unwrap().into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_mempool_account(account: u32) {
|
||||
api::set_mempool_account(account);
|
||||
pub unsafe extern "C" fn set_mempool_account(coin: u8, account: u32) {
|
||||
api::set_mempool_account(coin, account);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn new_account(name: *mut c_char, data: *mut c_char) -> i32 {
|
||||
pub unsafe extern "C" fn new_account(
|
||||
coin: u8,
|
||||
name: *mut c_char,
|
||||
data: *mut c_char,
|
||||
index: u32,
|
||||
) -> i32 {
|
||||
let name = CStr::from_ptr(name).to_string_lossy();
|
||||
let data = CStr::from_ptr(data).to_string_lossy();
|
||||
api::new_account(&name, &data)
|
||||
api::new_account(coin, &name, &data, index)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_mempool_balance() -> i64 {
|
||||
api::get_mempool_balance()
|
||||
pub unsafe extern "C" fn new_sub_account(coin: u8, id: u32, name: *mut c_char) -> i32 {
|
||||
let name = CStr::from_ptr(name).to_string_lossy();
|
||||
api::new_sub_account(coin, id, &name)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_mempool_balance(coin: u8) -> i64 {
|
||||
api::get_mempool_balance(coin)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn send_multi_payment(
|
||||
coin: u8,
|
||||
account: u32,
|
||||
recipients_json: *mut c_char,
|
||||
anchor_offset: u32,
|
||||
|
@ -78,6 +89,7 @@ pub unsafe extern "C" fn send_multi_payment(
|
|||
) -> *const c_char {
|
||||
let recipients_json = CStr::from_ptr(recipients_json).to_string_lossy();
|
||||
let tx_id = api::send_multi_payment(
|
||||
coin,
|
||||
account,
|
||||
&recipients_json,
|
||||
use_transparent,
|
||||
|
@ -87,78 +99,90 @@ pub unsafe extern "C" fn send_multi_payment(
|
|||
CString::new(tx_id).unwrap().into_raw()
|
||||
}
|
||||
|
||||
// #[no_mangle]
|
||||
// pub unsafe extern "C" fn try_warp_sync(coin: u8, get_tx: bool, anchor_offset: u32) -> i8 {
|
||||
// api::try_warp_sync(coin, get_tx, anchor_offset)
|
||||
// }
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn try_warp_sync(get_tx: bool, anchor_offset: u32) -> i8 {
|
||||
api::try_warp_sync(get_tx, anchor_offset)
|
||||
pub unsafe extern "C" fn skip_to_last_height(coin: u8) {
|
||||
api::skip_to_last_height(coin)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn skip_to_last_height() {
|
||||
api::skip_to_last_height()
|
||||
pub unsafe extern "C" fn rewind_to_height(coin: u8, height: u32) {
|
||||
api::rewind_to_height(coin, height)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rewind_to_height(height: u32) {
|
||||
api::rewind_to_height(height)
|
||||
pub unsafe extern "C" fn mempool_sync(coin: u8) -> i64 {
|
||||
api::mempool_sync(coin)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn mempool_sync() -> i64 {
|
||||
api::mempool_sync()
|
||||
pub unsafe extern "C" fn mempool_reset(coin: u8, height: u32) {
|
||||
api::mempool_reset(coin, height)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn mempool_reset(height: u32) {
|
||||
api::mempool_reset(height)
|
||||
pub unsafe extern "C" fn get_taddr_balance(coin: u8, account: u32) -> u64 {
|
||||
api::get_taddr_balance(coin, account)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_taddr_balance(account: u32) -> u64 {
|
||||
api::get_taddr_balance(account)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn shield_taddr(account: u32) -> *mut c_char {
|
||||
let tx_id = api::shield_taddr(account);
|
||||
pub unsafe extern "C" fn shield_taddr(coin: u8, account: u32) -> *mut c_char {
|
||||
let tx_id = api::shield_taddr(coin, account);
|
||||
CString::new(tx_id).unwrap().into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_lwd_url(url: *mut c_char) {
|
||||
pub unsafe extern "C" fn set_lwd_url(coin: u8, url: *mut c_char) {
|
||||
let url = CStr::from_ptr(url).to_string_lossy();
|
||||
api::set_lwd_url(&url);
|
||||
api::set_lwd_url(coin, &url);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn prepare_multi_payment(
|
||||
coin: u8,
|
||||
account: u32,
|
||||
recipients_json: *mut c_char,
|
||||
use_transparent: bool,
|
||||
anchor_offset: u32,
|
||||
) -> *mut c_char {
|
||||
let recipients_json = CStr::from_ptr(recipients_json).to_string_lossy();
|
||||
let tx = api::prepare_multi_payment(account, &recipients_json, use_transparent, anchor_offset);
|
||||
let tx = api::prepare_multi_payment(
|
||||
coin,
|
||||
account,
|
||||
&recipients_json,
|
||||
use_transparent,
|
||||
anchor_offset,
|
||||
);
|
||||
CString::new(tx).unwrap().into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn broadcast(tx_filename: *mut c_char) -> *mut c_char {
|
||||
pub unsafe extern "C" fn broadcast(coin: u8, tx_filename: *mut c_char) -> *mut c_char {
|
||||
let tx_filename = CStr::from_ptr(tx_filename).to_string_lossy();
|
||||
let res = api::broadcast(&tx_filename);
|
||||
let res = api::broadcast(coin, &tx_filename);
|
||||
CString::new(res).unwrap().into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn broadcast_txhex(txhex: *mut c_char) -> *mut c_char {
|
||||
pub unsafe extern "C" fn broadcast_txhex(coin: u8, txhex: *mut c_char) -> *mut c_char {
|
||||
let txhex = CStr::from_ptr(txhex).to_string_lossy();
|
||||
let res = api::broadcast_txhex(&txhex);
|
||||
let res = api::broadcast_txhex(coin, &txhex);
|
||||
CString::new(res).unwrap().into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sync_historical_prices(now: i64, days: u32, currency: *mut c_char) -> u32 {
|
||||
pub unsafe extern "C" fn sync_historical_prices(
|
||||
coin: u8,
|
||||
now: i64,
|
||||
days: u32,
|
||||
currency: *mut c_char,
|
||||
) -> u32 {
|
||||
let currency = CStr::from_ptr(currency).to_string_lossy();
|
||||
api::sync_historical_prices(now, days, ¤cy)
|
||||
api::sync_historical_prices(coin, now, days, ¤cy)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -181,6 +205,7 @@ pub unsafe extern "C" fn get_sapling(ua_addr: *mut c_char) -> *mut c_char {
|
|||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn store_contact(
|
||||
coin: u8,
|
||||
id: u32,
|
||||
name: *mut c_char,
|
||||
address: *mut c_char,
|
||||
|
@ -188,41 +213,46 @@ pub unsafe extern "C" fn store_contact(
|
|||
) {
|
||||
let name = CStr::from_ptr(name).to_string_lossy();
|
||||
let address = CStr::from_ptr(address).to_string_lossy();
|
||||
api::store_contact(id, &name, &address, dirty);
|
||||
api::store_contact(coin, id, &name, &address, dirty);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn commit_unsaved_contacts(account: u32, anchor_offset: u32) -> *mut c_char {
|
||||
let tx_id = api::commit_unsaved_contacts(account, anchor_offset);
|
||||
pub unsafe extern "C" fn commit_unsaved_contacts(
|
||||
coin: u8,
|
||||
account: u32,
|
||||
anchor_offset: u32,
|
||||
) -> *mut c_char {
|
||||
let tx_id = api::commit_unsaved_contacts(coin, account, anchor_offset);
|
||||
CString::new(tx_id).unwrap().into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn delete_account(account: u32) {
|
||||
api::delete_account(account);
|
||||
pub unsafe extern "C" fn delete_account(coin: u8, account: u32) {
|
||||
api::delete_account(coin, account);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn truncate_data() {
|
||||
api::truncate_data();
|
||||
pub unsafe extern "C" fn truncate_data(coin: u8) {
|
||||
api::truncate_data(coin);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn make_payment_uri(
|
||||
coin: u8,
|
||||
address: *mut c_char,
|
||||
amount: u64,
|
||||
memo: *mut c_char,
|
||||
) -> *mut c_char {
|
||||
let address = CStr::from_ptr(address).to_string_lossy();
|
||||
let memo = CStr::from_ptr(memo).to_string_lossy();
|
||||
let uri = api::make_payment_uri(&address, amount, &memo);
|
||||
let uri = api::make_payment_uri(coin, &address, amount, &memo);
|
||||
CString::new(uri).unwrap().into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn parse_payment_uri(uri: *mut c_char) -> *mut c_char {
|
||||
pub unsafe extern "C" fn parse_payment_uri(coin: u8, uri: *mut c_char) -> *mut c_char {
|
||||
let uri = CStr::from_ptr(uri).to_string_lossy();
|
||||
let payment_json = api::parse_payment_uri(&uri);
|
||||
let payment_json = api::parse_payment_uri(coin, &uri);
|
||||
CString::new(payment_json).unwrap().into_raw()
|
||||
}
|
||||
|
||||
|
@ -248,14 +278,14 @@ pub unsafe extern "C" fn restore_full_backup(key: *mut c_char, backup: *mut c_ch
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn store_share_secret(account: u32, secret: *mut c_char) {
|
||||
pub unsafe extern "C" fn store_share_secret(coin: u8, account: u32, secret: *mut c_char) {
|
||||
let secret = CStr::from_ptr(secret).to_string_lossy();
|
||||
api::store_share_secret(account, &secret);
|
||||
api::store_share_secret(coin, account, &secret);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_share_secret(account: u32) -> *mut c_char {
|
||||
let secret = api::get_share_secret(account);
|
||||
pub unsafe extern "C" fn get_share_secret(coin: u8, account: u32) -> *mut c_char {
|
||||
let secret = api::get_share_secret(coin, account);
|
||||
CString::new(secret).unwrap().into_raw()
|
||||
}
|
||||
|
||||
|
@ -303,11 +333,12 @@ pub unsafe extern "C" fn run_multi_signer(
|
|||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn split_account(
|
||||
coin: u8,
|
||||
threshold: u32,
|
||||
participants: u32,
|
||||
account: u32,
|
||||
) -> *mut c_char {
|
||||
let r = api::split_account(threshold, participants, account);
|
||||
let r = api::split_account(coin, threshold, participants, account);
|
||||
CString::new(r).unwrap().into_raw()
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2bccb5267a7715d974f51f3e70f6866495f65e5e
|
||||
Subproject commit c26e1525c88522eb69896b79f6553d2cb46eb800
|
|
@ -1 +1 @@
|
|||
Subproject commit 766748ccfb9c69f8bccaeb0b7c4b02b5a9777e71
|
||||
Subproject commit 056ffa454aeb8dd6dc8a60ac02379e34557c7740
|
|
@ -15,28 +15,49 @@ typedef report_callback = Void Function(Int32);
|
|||
const DAY_MS = 24 * 3600 * 1000;
|
||||
|
||||
class SyncParams {
|
||||
int coin;
|
||||
bool getTx;
|
||||
int anchorOffset;
|
||||
SendPort? port;
|
||||
|
||||
SyncParams(this.getTx, this.anchorOffset, this.port);
|
||||
SyncParams(this.coin, this.getTx, this.anchorOffset, this.port);
|
||||
}
|
||||
|
||||
class PaymentParams {
|
||||
int coin;
|
||||
int account;
|
||||
String recipientsJson;
|
||||
bool useTransparent;
|
||||
int anchorOffset;
|
||||
SendPort port;
|
||||
|
||||
PaymentParams(this.account, this.recipientsJson, this.useTransparent, this.anchorOffset, this.port);
|
||||
PaymentParams(this.coin, this.account, this.recipientsJson, this.useTransparent, this.anchorOffset, this.port);
|
||||
}
|
||||
|
||||
class ShieldTAddrParams {
|
||||
int coin;
|
||||
int account;
|
||||
ShieldTAddrParams(this.coin, this.account);
|
||||
}
|
||||
|
||||
class CommitContactsParams {
|
||||
int coin;
|
||||
int account;
|
||||
int anchorOffset;
|
||||
|
||||
CommitContactsParams(this.account, this.anchorOffset);
|
||||
CommitContactsParams(this.coin, this.account, this.anchorOffset);
|
||||
}
|
||||
|
||||
class SyncHistoricalPricesParams {
|
||||
int coin;
|
||||
String currency;
|
||||
SyncHistoricalPricesParams(this.coin, this.currency);
|
||||
}
|
||||
|
||||
class GetTBalanceParams {
|
||||
int coin;
|
||||
int account;
|
||||
GetTBalanceParams(this.coin, this.account);
|
||||
}
|
||||
|
||||
const DEFAULT_ACCOUNT = 1;
|
||||
|
@ -63,60 +84,61 @@ class WarpApi {
|
|||
throw UnsupportedError('This platform is not supported.');
|
||||
}
|
||||
|
||||
static initWallet(String dbPath, String ldUrl) {
|
||||
static initWallet(String dbPath) {
|
||||
warp_api_lib.init_wallet(
|
||||
dbPath.toNativeUtf8().cast<Int8>(), ldUrl.toNativeUtf8().cast<Int8>());
|
||||
dbPath.toNativeUtf8().cast<Int8>());
|
||||
}
|
||||
|
||||
static void resetApp() {
|
||||
warp_api_lib.reset_app();
|
||||
}
|
||||
|
||||
static int newAccount(String name, String key) {
|
||||
return warp_api_lib.new_account(
|
||||
name.toNativeUtf8().cast<Int8>(), key.toNativeUtf8().cast<Int8>());
|
||||
static int newAccount(int coin, String name, String key, int index) {
|
||||
return warp_api_lib.new_account(coin,
|
||||
name.toNativeUtf8().cast<Int8>(), key.toNativeUtf8().cast<Int8>(), index);
|
||||
}
|
||||
|
||||
static void skipToLastHeight() {
|
||||
warp_api_lib.skip_to_last_height();
|
||||
static int newSubAccount(int coin, int accountId, String name) {
|
||||
return warp_api_lib.new_sub_account(coin, accountId,
|
||||
name.toNativeUtf8().cast<Int8>());
|
||||
}
|
||||
|
||||
static void rewindToHeight(int height) {
|
||||
warp_api_lib.rewind_to_height(height);
|
||||
static void skipToLastHeight(int coin) {
|
||||
warp_api_lib.skip_to_last_height(coin);
|
||||
}
|
||||
|
||||
static void warpSync(SyncParams params) {
|
||||
warp_api_lib.warp_sync(params.getTx ? 1 : 0, params.anchorOffset, params.port!.nativePort);
|
||||
params.port!.send(-1);
|
||||
static void rewindToHeight(int coin, int height) {
|
||||
warp_api_lib.rewind_to_height(coin, height);
|
||||
}
|
||||
|
||||
static Future<int> tryWarpSync(bool getTx, int anchorOffset) async {
|
||||
final res = await compute(tryWarpSyncIsolateFn, SyncParams(getTx, anchorOffset, null));
|
||||
static int warpSync(SyncParams params) {
|
||||
final res = warp_api_lib.warp_sync(params.coin, params.getTx ? 1 : 0, params.anchorOffset, params.port!.nativePort);
|
||||
params.port!.send(null);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void mempoolReset(int height) {
|
||||
warp_api_lib.mempool_reset(height);
|
||||
static void mempoolReset(int coin, int height) {
|
||||
warp_api_lib.mempool_reset(coin, height);
|
||||
}
|
||||
|
||||
static Future<int> mempoolSync() async {
|
||||
return compute(mempoolSyncIsolateFn, null);
|
||||
static Future<int> mempoolSync(int coin) async {
|
||||
return compute(mempoolSyncIsolateFn, coin);
|
||||
}
|
||||
|
||||
static Future<int> getLatestHeight() async {
|
||||
return await compute(getLatestHeightIsolateFn, null);
|
||||
static Future<int> getLatestHeight(int coin) async {
|
||||
return await compute(getLatestHeightIsolateFn, coin);
|
||||
}
|
||||
|
||||
static int validKey(String key) {
|
||||
return warp_api_lib.is_valid_key(key.toNativeUtf8().cast<Int8>());
|
||||
static int validKey(int coin, String key) {
|
||||
return warp_api_lib.is_valid_key(coin, key.toNativeUtf8().cast<Int8>());
|
||||
}
|
||||
|
||||
static bool validAddress(String address) {
|
||||
return warp_api_lib.valid_address(address.toNativeUtf8().cast<Int8>()) != 0;
|
||||
static bool validAddress(int coin, String address) {
|
||||
return warp_api_lib.valid_address(coin, address.toNativeUtf8().cast<Int8>()) != 0;
|
||||
}
|
||||
|
||||
static String newAddress(int account) {
|
||||
final address = warp_api_lib.new_address(account);
|
||||
static String newAddress(int coin, int account) {
|
||||
final address = warp_api_lib.new_address(coin, account);
|
||||
return address.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
|
@ -130,15 +152,15 @@ class WarpApi {
|
|||
return zaddr.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
static void setMempoolAccount(int account) {
|
||||
return warp_api_lib.set_mempool_account(account);
|
||||
static void setMempoolAccount(int coin, int account) {
|
||||
return warp_api_lib.set_mempool_account(coin, account);
|
||||
}
|
||||
|
||||
static int getUnconfirmedBalance() {
|
||||
return warp_api_lib.get_mempool_balance();
|
||||
static int getUnconfirmedBalance(int coin) {
|
||||
return warp_api_lib.get_mempool_balance(coin);
|
||||
}
|
||||
|
||||
static Future<String> sendPayment(int account, List<Recipient> recipients, bool useTransparent, int anchorOffset, void Function(int) f) async {
|
||||
static Future<String> sendPayment(int coin, int account, List<Recipient> recipients, bool useTransparent, int anchorOffset, void Function(int) f) async {
|
||||
var receivePort = ReceivePort();
|
||||
receivePort.listen((progress) {
|
||||
f(progress);
|
||||
|
@ -149,81 +171,84 @@ class WarpApi {
|
|||
return await compute(
|
||||
sendPaymentIsolateFn,
|
||||
PaymentParams(
|
||||
account, recipientJson, useTransparent, anchorOffset, receivePort.sendPort));
|
||||
coin, account, recipientJson, useTransparent, anchorOffset, receivePort.sendPort));
|
||||
}
|
||||
|
||||
static int getTBalance(int account) {
|
||||
final balance = warp_api_lib.get_taddr_balance(account);
|
||||
static int getTBalance(int coin, int account) {
|
||||
final balance = warp_api_lib.get_taddr_balance(coin, account);
|
||||
return balance;
|
||||
}
|
||||
|
||||
static Future<int> getTBalanceAsync(int account) async {
|
||||
final balance = await compute(getTBalanceIsolateFn, account);
|
||||
static Future<int> getTBalanceAsync(int coin, int account) async {
|
||||
final balance = await compute(getTBalanceIsolateFn, GetTBalanceParams(coin, account));
|
||||
return balance;
|
||||
}
|
||||
|
||||
static Future<String> shieldTAddr(int account) async {
|
||||
final txId = compute(shieldTAddrIsolateFn, account);
|
||||
static Future<String> shieldTAddr(int coin, int account) async {
|
||||
final txId = compute(shieldTAddrIsolateFn, ShieldTAddrParams(coin, account));
|
||||
return txId;
|
||||
}
|
||||
|
||||
static String prepareTx(
|
||||
int coin,
|
||||
int account,
|
||||
List<Recipient> recipients,
|
||||
bool useTransparent,
|
||||
int anchorOffset,
|
||||
String txFilename) {
|
||||
final recipientsJson = jsonEncode(recipients);
|
||||
final res = warp_api_lib.prepare_multi_payment(account,
|
||||
final res = warp_api_lib.prepare_multi_payment(coin, account,
|
||||
recipientsJson.toNativeUtf8().cast<Int8>(),
|
||||
useTransparent ? 1 : 0, anchorOffset);
|
||||
return res.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
static String broadcast(String txFilename) {
|
||||
final res = warp_api_lib.broadcast(txFilename.toNativeUtf8().cast<Int8>());
|
||||
static String broadcast(int coin, String txFilename) {
|
||||
final res = warp_api_lib.broadcast(coin, txFilename.toNativeUtf8().cast<Int8>());
|
||||
return res.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
static String broadcastHex(String tx) {
|
||||
final res = warp_api_lib.broadcast_txhex(tx.toNativeUtf8().cast<Int8>());
|
||||
static String broadcastHex(int coin, String tx) {
|
||||
final res = warp_api_lib.broadcast_txhex(coin, tx.toNativeUtf8().cast<Int8>());
|
||||
return res.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
static Future<int> syncHistoricalPrices(String currency) async {
|
||||
return await compute(syncHistoricalPricesIsolateFn, currency);
|
||||
static Future<int> syncHistoricalPrices(int coin, String currency) async {
|
||||
return await compute(syncHistoricalPricesIsolateFn, SyncHistoricalPricesParams(coin, currency));
|
||||
}
|
||||
|
||||
static updateLWD(String url) {
|
||||
warp_api_lib.set_lwd_url(url.toNativeUtf8().cast<Int8>());
|
||||
static updateLWD(int coin, String url) {
|
||||
warp_api_lib.set_lwd_url(coin, url.toNativeUtf8().cast<Int8>());
|
||||
}
|
||||
|
||||
static void storeContact(int id, String name, String address, bool dirty) {
|
||||
warp_api_lib.store_contact(id, name.toNativeUtf8().cast<Int8>(), address.toNativeUtf8().cast<Int8>(), dirty ? 1 : 0);
|
||||
static void storeContact(int coin, int id, String name, String address, bool dirty) {
|
||||
warp_api_lib.store_contact(coin, id, name.toNativeUtf8().cast<Int8>(), address.toNativeUtf8().cast<Int8>(), dirty ? 1 : 0);
|
||||
}
|
||||
|
||||
static Future<String> commitUnsavedContacts(int account, int anchorOffset) async {
|
||||
return compute(commitUnsavedContactsIsolateFn, CommitContactsParams(account, anchorOffset));
|
||||
static Future<String> commitUnsavedContacts(int coin, int account, int anchorOffset) async {
|
||||
return compute(commitUnsavedContactsIsolateFn, CommitContactsParams(coin, account, anchorOffset));
|
||||
}
|
||||
|
||||
static void truncateData() {
|
||||
warp_api_lib.truncate_data();
|
||||
static void truncateData(int coin) {
|
||||
warp_api_lib.truncate_data(coin);
|
||||
}
|
||||
|
||||
static void deleteAccount(int account) {
|
||||
warp_api_lib.delete_account(account);
|
||||
static void deleteAccount(int coin, int account) {
|
||||
warp_api_lib.delete_account(coin, account);
|
||||
}
|
||||
|
||||
static String makePaymentURI(String address, int amount, String memo) {
|
||||
static String makePaymentURI(int coin, String address, int amount, String memo) {
|
||||
final uri = warp_api_lib.make_payment_uri(
|
||||
coin,
|
||||
address.toNativeUtf8().cast<Int8>(),
|
||||
amount,
|
||||
memo.toNativeUtf8().cast<Int8>());
|
||||
return uri.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
static String parsePaymentURI(String uri) {
|
||||
static String parsePaymentURI(int coin, String uri) {
|
||||
final json = warp_api_lib.parse_payment_uri(
|
||||
coin,
|
||||
uri.toNativeUtf8().cast<Int8>());
|
||||
return json.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
@ -242,8 +267,8 @@ class WarpApi {
|
|||
return res.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
static void storeShareSecret(int account, String secret) {
|
||||
warp_api_lib.store_share_secret(account, secret.toNativeUtf8().cast<Int8>());
|
||||
static void storeShareSecret(int coin, int account, String secret) {
|
||||
warp_api_lib.store_share_secret(coin, account, secret.toNativeUtf8().cast<Int8>());
|
||||
}
|
||||
|
||||
static Future<void> runAggregator(String secretShare, int port, SendPort sendPort) async {
|
||||
|
@ -262,8 +287,8 @@ class WarpApi {
|
|||
warp_api_lib.shutdown_aggregator();
|
||||
}
|
||||
|
||||
static String splitAccount(int threshold, int participants, int account) {
|
||||
return warp_api_lib.split_account(threshold, participants, account).cast<Utf8>().toDartString();
|
||||
static String splitAccount(int coin, int threshold, int participants, int account) {
|
||||
return warp_api_lib.split_account(coin, threshold, participants, account).cast<Utf8>().toDartString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,6 +333,7 @@ int runMultiSignerIsolateFn(RunMultiSignerParams params) {
|
|||
|
||||
String sendPaymentIsolateFn(PaymentParams params) {
|
||||
final txId = warp_api_lib.send_multi_payment(
|
||||
params.coin,
|
||||
params.account,
|
||||
params.recipientsJson.toNativeUtf8().cast<Int8>(),
|
||||
params.anchorOffset,
|
||||
|
@ -316,35 +342,31 @@ String sendPaymentIsolateFn(PaymentParams params) {
|
|||
return txId.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
int tryWarpSyncIsolateFn(SyncParams params) {
|
||||
return warp_api_lib.try_warp_sync(params.getTx ? 1 : 0, params.anchorOffset);
|
||||
int getLatestHeightIsolateFn(int coin) {
|
||||
return warp_api_lib.get_latest_height(coin);
|
||||
}
|
||||
|
||||
int getLatestHeightIsolateFn(Null _dummy) {
|
||||
return warp_api_lib.get_latest_height();
|
||||
int mempoolSyncIsolateFn(int coin) {
|
||||
return warp_api_lib.mempool_sync(coin);
|
||||
}
|
||||
|
||||
int mempoolSyncIsolateFn(Null _dummy) {
|
||||
return warp_api_lib.mempool_sync();
|
||||
}
|
||||
|
||||
String shieldTAddrIsolateFn(int account) {
|
||||
final txId = warp_api_lib.shield_taddr(account);
|
||||
String shieldTAddrIsolateFn(ShieldTAddrParams params) {
|
||||
final txId = warp_api_lib.shield_taddr(params.coin, params.account);
|
||||
return txId.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
int syncHistoricalPricesIsolateFn(String currency) {
|
||||
int syncHistoricalPricesIsolateFn(SyncHistoricalPricesParams params) {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime.utc(now.year, now.month, now.day);
|
||||
return warp_api_lib.sync_historical_prices(today.millisecondsSinceEpoch ~/ 1000, 365, currency.toNativeUtf8().cast<Int8>());
|
||||
return warp_api_lib.sync_historical_prices(params.coin, today.millisecondsSinceEpoch ~/ 1000, 365, params.currency.toNativeUtf8().cast<Int8>());
|
||||
}
|
||||
|
||||
String commitUnsavedContactsIsolateFn(CommitContactsParams params) {
|
||||
final txId = warp_api_lib.commit_unsaved_contacts(params.account, params.anchorOffset);
|
||||
final txId = warp_api_lib.commit_unsaved_contacts(params.coin, params.account, params.anchorOffset);
|
||||
return txId.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
int getTBalanceIsolateFn(int account) {
|
||||
return warp_api_lib.get_taddr_balance(account);
|
||||
int getTBalanceIsolateFn(GetTBalanceParams params) {
|
||||
return warp_api_lib.get_taddr_balance(params.coin, params.account);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,11 +20,9 @@ class NativeLibrary {
|
|||
|
||||
void init_wallet(
|
||||
ffi.Pointer<ffi.Int8> db_path,
|
||||
ffi.Pointer<ffi.Int8> ld_url,
|
||||
) {
|
||||
return _init_wallet(
|
||||
db_path,
|
||||
ld_url,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -42,12 +40,14 @@ class NativeLibrary {
|
|||
late final _dart_reset_app _reset_app =
|
||||
_reset_app_ptr.asFunction<_dart_reset_app>();
|
||||
|
||||
void warp_sync(
|
||||
int warp_sync(
|
||||
int coin,
|
||||
int get_tx,
|
||||
int anchor_offset,
|
||||
int port,
|
||||
) {
|
||||
return _warp_sync(
|
||||
coin,
|
||||
get_tx,
|
||||
anchor_offset,
|
||||
port,
|
||||
|
@ -72,8 +72,12 @@ class NativeLibrary {
|
|||
late final _dart_dart_post_cobject _dart_post_cobject =
|
||||
_dart_post_cobject_ptr.asFunction<_dart_dart_post_cobject>();
|
||||
|
||||
int get_latest_height() {
|
||||
return _get_latest_height();
|
||||
int get_latest_height(
|
||||
int coin,
|
||||
) {
|
||||
return _get_latest_height(
|
||||
coin,
|
||||
);
|
||||
}
|
||||
|
||||
late final _get_latest_height_ptr =
|
||||
|
@ -82,9 +86,11 @@ class NativeLibrary {
|
|||
_get_latest_height_ptr.asFunction<_dart_get_latest_height>();
|
||||
|
||||
int is_valid_key(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> seed,
|
||||
) {
|
||||
return _is_valid_key(
|
||||
coin,
|
||||
seed,
|
||||
);
|
||||
}
|
||||
|
@ -95,9 +101,11 @@ class NativeLibrary {
|
|||
_is_valid_key_ptr.asFunction<_dart_is_valid_key>();
|
||||
|
||||
int valid_address(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
) {
|
||||
return _valid_address(
|
||||
coin,
|
||||
address,
|
||||
);
|
||||
}
|
||||
|
@ -108,9 +116,11 @@ class NativeLibrary {
|
|||
_valid_address_ptr.asFunction<_dart_valid_address>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> new_address(
|
||||
int coin,
|
||||
int account,
|
||||
) {
|
||||
return _new_address(
|
||||
coin,
|
||||
account,
|
||||
);
|
||||
}
|
||||
|
@ -121,9 +131,11 @@ class NativeLibrary {
|
|||
_new_address_ptr.asFunction<_dart_new_address>();
|
||||
|
||||
void set_mempool_account(
|
||||
int coin,
|
||||
int account,
|
||||
) {
|
||||
return _set_mempool_account(
|
||||
coin,
|
||||
account,
|
||||
);
|
||||
}
|
||||
|
@ -135,12 +147,16 @@ class NativeLibrary {
|
|||
_set_mempool_account_ptr.asFunction<_dart_set_mempool_account>();
|
||||
|
||||
int new_account(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
ffi.Pointer<ffi.Int8> data,
|
||||
int index,
|
||||
) {
|
||||
return _new_account(
|
||||
coin,
|
||||
name,
|
||||
data,
|
||||
index,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -149,8 +165,29 @@ class NativeLibrary {
|
|||
late final _dart_new_account _new_account =
|
||||
_new_account_ptr.asFunction<_dart_new_account>();
|
||||
|
||||
int get_mempool_balance() {
|
||||
return _get_mempool_balance();
|
||||
int new_sub_account(
|
||||
int coin,
|
||||
int id,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
) {
|
||||
return _new_sub_account(
|
||||
coin,
|
||||
id,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
late final _new_sub_account_ptr =
|
||||
_lookup<ffi.NativeFunction<_c_new_sub_account>>('new_sub_account');
|
||||
late final _dart_new_sub_account _new_sub_account =
|
||||
_new_sub_account_ptr.asFunction<_dart_new_sub_account>();
|
||||
|
||||
int get_mempool_balance(
|
||||
int coin,
|
||||
) {
|
||||
return _get_mempool_balance(
|
||||
coin,
|
||||
);
|
||||
}
|
||||
|
||||
late final _get_mempool_balance_ptr =
|
||||
|
@ -160,6 +197,7 @@ class NativeLibrary {
|
|||
_get_mempool_balance_ptr.asFunction<_dart_get_mempool_balance>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> send_multi_payment(
|
||||
int coin,
|
||||
int account,
|
||||
ffi.Pointer<ffi.Int8> recipients_json,
|
||||
int anchor_offset,
|
||||
|
@ -167,6 +205,7 @@ class NativeLibrary {
|
|||
int port,
|
||||
) {
|
||||
return _send_multi_payment(
|
||||
coin,
|
||||
account,
|
||||
recipients_json,
|
||||
anchor_offset,
|
||||
|
@ -180,25 +219,14 @@ class NativeLibrary {
|
|||
late final _dart_send_multi_payment _send_multi_payment =
|
||||
_send_multi_payment_ptr.asFunction<_dart_send_multi_payment>();
|
||||
|
||||
int try_warp_sync(
|
||||
int get_tx,
|
||||
int anchor_offset,
|
||||
void skip_to_last_height(
|
||||
int coin,
|
||||
) {
|
||||
return _try_warp_sync(
|
||||
get_tx,
|
||||
anchor_offset,
|
||||
return _skip_to_last_height(
|
||||
coin,
|
||||
);
|
||||
}
|
||||
|
||||
late final _try_warp_sync_ptr =
|
||||
_lookup<ffi.NativeFunction<_c_try_warp_sync>>('try_warp_sync');
|
||||
late final _dart_try_warp_sync _try_warp_sync =
|
||||
_try_warp_sync_ptr.asFunction<_dart_try_warp_sync>();
|
||||
|
||||
void skip_to_last_height() {
|
||||
return _skip_to_last_height();
|
||||
}
|
||||
|
||||
late final _skip_to_last_height_ptr =
|
||||
_lookup<ffi.NativeFunction<_c_skip_to_last_height>>(
|
||||
'skip_to_last_height');
|
||||
|
@ -206,9 +234,11 @@ class NativeLibrary {
|
|||
_skip_to_last_height_ptr.asFunction<_dart_skip_to_last_height>();
|
||||
|
||||
void rewind_to_height(
|
||||
int coin,
|
||||
int height,
|
||||
) {
|
||||
return _rewind_to_height(
|
||||
coin,
|
||||
height,
|
||||
);
|
||||
}
|
||||
|
@ -218,8 +248,12 @@ class NativeLibrary {
|
|||
late final _dart_rewind_to_height _rewind_to_height =
|
||||
_rewind_to_height_ptr.asFunction<_dart_rewind_to_height>();
|
||||
|
||||
int mempool_sync() {
|
||||
return _mempool_sync();
|
||||
int mempool_sync(
|
||||
int coin,
|
||||
) {
|
||||
return _mempool_sync(
|
||||
coin,
|
||||
);
|
||||
}
|
||||
|
||||
late final _mempool_sync_ptr =
|
||||
|
@ -228,9 +262,11 @@ class NativeLibrary {
|
|||
_mempool_sync_ptr.asFunction<_dart_mempool_sync>();
|
||||
|
||||
void mempool_reset(
|
||||
int coin,
|
||||
int height,
|
||||
) {
|
||||
return _mempool_reset(
|
||||
coin,
|
||||
height,
|
||||
);
|
||||
}
|
||||
|
@ -241,9 +277,11 @@ class NativeLibrary {
|
|||
_mempool_reset_ptr.asFunction<_dart_mempool_reset>();
|
||||
|
||||
int get_taddr_balance(
|
||||
int coin,
|
||||
int account,
|
||||
) {
|
||||
return _get_taddr_balance(
|
||||
coin,
|
||||
account,
|
||||
);
|
||||
}
|
||||
|
@ -254,9 +292,11 @@ class NativeLibrary {
|
|||
_get_taddr_balance_ptr.asFunction<_dart_get_taddr_balance>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> shield_taddr(
|
||||
int coin,
|
||||
int account,
|
||||
) {
|
||||
return _shield_taddr(
|
||||
coin,
|
||||
account,
|
||||
);
|
||||
}
|
||||
|
@ -267,9 +307,11 @@ class NativeLibrary {
|
|||
_shield_taddr_ptr.asFunction<_dart_shield_taddr>();
|
||||
|
||||
void set_lwd_url(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> url,
|
||||
) {
|
||||
return _set_lwd_url(
|
||||
coin,
|
||||
url,
|
||||
);
|
||||
}
|
||||
|
@ -280,12 +322,14 @@ class NativeLibrary {
|
|||
_set_lwd_url_ptr.asFunction<_dart_set_lwd_url>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> prepare_multi_payment(
|
||||
int coin,
|
||||
int account,
|
||||
ffi.Pointer<ffi.Int8> recipients_json,
|
||||
int use_transparent,
|
||||
int anchor_offset,
|
||||
) {
|
||||
return _prepare_multi_payment(
|
||||
coin,
|
||||
account,
|
||||
recipients_json,
|
||||
use_transparent,
|
||||
|
@ -300,9 +344,11 @@ class NativeLibrary {
|
|||
_prepare_multi_payment_ptr.asFunction<_dart_prepare_multi_payment>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> broadcast(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> tx_filename,
|
||||
) {
|
||||
return _broadcast(
|
||||
coin,
|
||||
tx_filename,
|
||||
);
|
||||
}
|
||||
|
@ -313,9 +359,11 @@ class NativeLibrary {
|
|||
_broadcast_ptr.asFunction<_dart_broadcast>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> broadcast_txhex(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> txhex,
|
||||
) {
|
||||
return _broadcast_txhex(
|
||||
coin,
|
||||
txhex,
|
||||
);
|
||||
}
|
||||
|
@ -326,11 +374,13 @@ class NativeLibrary {
|
|||
_broadcast_txhex_ptr.asFunction<_dart_broadcast_txhex>();
|
||||
|
||||
int sync_historical_prices(
|
||||
int coin,
|
||||
int now,
|
||||
int days,
|
||||
ffi.Pointer<ffi.Int8> currency,
|
||||
) {
|
||||
return _sync_historical_prices(
|
||||
coin,
|
||||
now,
|
||||
days,
|
||||
currency,
|
||||
|
@ -370,12 +420,14 @@ class NativeLibrary {
|
|||
_get_sapling_ptr.asFunction<_dart_get_sapling>();
|
||||
|
||||
void store_contact(
|
||||
int coin,
|
||||
int id,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
int dirty,
|
||||
) {
|
||||
return _store_contact(
|
||||
coin,
|
||||
id,
|
||||
name,
|
||||
address,
|
||||
|
@ -389,10 +441,12 @@ class NativeLibrary {
|
|||
_store_contact_ptr.asFunction<_dart_store_contact>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> commit_unsaved_contacts(
|
||||
int coin,
|
||||
int account,
|
||||
int anchor_offset,
|
||||
) {
|
||||
return _commit_unsaved_contacts(
|
||||
coin,
|
||||
account,
|
||||
anchor_offset,
|
||||
);
|
||||
|
@ -405,9 +459,11 @@ class NativeLibrary {
|
|||
_commit_unsaved_contacts_ptr.asFunction<_dart_commit_unsaved_contacts>();
|
||||
|
||||
void delete_account(
|
||||
int coin,
|
||||
int account,
|
||||
) {
|
||||
return _delete_account(
|
||||
coin,
|
||||
account,
|
||||
);
|
||||
}
|
||||
|
@ -417,8 +473,12 @@ class NativeLibrary {
|
|||
late final _dart_delete_account _delete_account =
|
||||
_delete_account_ptr.asFunction<_dart_delete_account>();
|
||||
|
||||
void truncate_data() {
|
||||
return _truncate_data();
|
||||
void truncate_data(
|
||||
int coin,
|
||||
) {
|
||||
return _truncate_data(
|
||||
coin,
|
||||
);
|
||||
}
|
||||
|
||||
late final _truncate_data_ptr =
|
||||
|
@ -427,11 +487,13 @@ class NativeLibrary {
|
|||
_truncate_data_ptr.asFunction<_dart_truncate_data>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> make_payment_uri(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
int amount,
|
||||
ffi.Pointer<ffi.Int8> memo,
|
||||
) {
|
||||
return _make_payment_uri(
|
||||
coin,
|
||||
address,
|
||||
amount,
|
||||
memo,
|
||||
|
@ -444,9 +506,11 @@ class NativeLibrary {
|
|||
_make_payment_uri_ptr.asFunction<_dart_make_payment_uri>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> parse_payment_uri(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> uri,
|
||||
) {
|
||||
return _parse_payment_uri(
|
||||
coin,
|
||||
uri,
|
||||
);
|
||||
}
|
||||
|
@ -496,10 +560,12 @@ class NativeLibrary {
|
|||
_restore_full_backup_ptr.asFunction<_dart_restore_full_backup>();
|
||||
|
||||
void store_share_secret(
|
||||
int coin,
|
||||
int account,
|
||||
ffi.Pointer<ffi.Int8> secret,
|
||||
) {
|
||||
return _store_share_secret(
|
||||
coin,
|
||||
account,
|
||||
secret,
|
||||
);
|
||||
|
@ -511,9 +577,11 @@ class NativeLibrary {
|
|||
_store_share_secret_ptr.asFunction<_dart_store_share_secret>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> get_share_secret(
|
||||
int coin,
|
||||
int account,
|
||||
) {
|
||||
return _get_share_secret(
|
||||
coin,
|
||||
account,
|
||||
);
|
||||
}
|
||||
|
@ -589,11 +657,13 @@ class NativeLibrary {
|
|||
_run_multi_signer_ptr.asFunction<_dart_run_multi_signer>();
|
||||
|
||||
ffi.Pointer<ffi.Int8> split_account(
|
||||
int coin,
|
||||
int threshold,
|
||||
int participants,
|
||||
int account,
|
||||
) {
|
||||
return _split_account(
|
||||
coin,
|
||||
threshold,
|
||||
participants,
|
||||
account,
|
||||
|
@ -617,25 +687,25 @@ class NativeLibrary {
|
|||
|
||||
typedef _c_init_wallet = ffi.Void Function(
|
||||
ffi.Pointer<ffi.Int8> db_path,
|
||||
ffi.Pointer<ffi.Int8> ld_url,
|
||||
);
|
||||
|
||||
typedef _dart_init_wallet = void Function(
|
||||
ffi.Pointer<ffi.Int8> db_path,
|
||||
ffi.Pointer<ffi.Int8> ld_url,
|
||||
);
|
||||
|
||||
typedef _c_reset_app = ffi.Void Function();
|
||||
|
||||
typedef _dart_reset_app = void Function();
|
||||
|
||||
typedef _c_warp_sync = ffi.Void Function(
|
||||
typedef _c_warp_sync = ffi.Int8 Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Int8 get_tx,
|
||||
ffi.Uint32 anchor_offset,
|
||||
ffi.Int64 port,
|
||||
);
|
||||
|
||||
typedef _dart_warp_sync = void Function(
|
||||
typedef _dart_warp_sync = int Function(
|
||||
int coin,
|
||||
int get_tx,
|
||||
int anchor_offset,
|
||||
int port,
|
||||
|
@ -649,57 +719,90 @@ typedef _dart_dart_post_cobject = void Function(
|
|||
ffi.Pointer<ffi.Void> ptr,
|
||||
);
|
||||
|
||||
typedef _c_get_latest_height = ffi.Uint32 Function();
|
||||
typedef _c_get_latest_height = ffi.Uint32 Function(
|
||||
ffi.Uint8 coin,
|
||||
);
|
||||
|
||||
typedef _dart_get_latest_height = int Function();
|
||||
typedef _dart_get_latest_height = int Function(
|
||||
int coin,
|
||||
);
|
||||
|
||||
typedef _c_is_valid_key = ffi.Int8 Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> seed,
|
||||
);
|
||||
|
||||
typedef _dart_is_valid_key = int Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> seed,
|
||||
);
|
||||
|
||||
typedef _c_valid_address = ffi.Int8 Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
);
|
||||
|
||||
typedef _dart_valid_address = int Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
);
|
||||
|
||||
typedef _c_new_address = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
);
|
||||
|
||||
typedef _dart_new_address = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
int account,
|
||||
);
|
||||
|
||||
typedef _c_set_mempool_account = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
);
|
||||
|
||||
typedef _dart_set_mempool_account = void Function(
|
||||
int coin,
|
||||
int account,
|
||||
);
|
||||
|
||||
typedef _c_new_account = ffi.Int32 Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
ffi.Pointer<ffi.Int8> data,
|
||||
ffi.Uint32 index,
|
||||
);
|
||||
|
||||
typedef _dart_new_account = int Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
ffi.Pointer<ffi.Int8> data,
|
||||
int index,
|
||||
);
|
||||
|
||||
typedef _c_get_mempool_balance = ffi.Int64 Function();
|
||||
typedef _c_new_sub_account = ffi.Int32 Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 id,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
);
|
||||
|
||||
typedef _dart_get_mempool_balance = int Function();
|
||||
typedef _dart_new_sub_account = int Function(
|
||||
int coin,
|
||||
int id,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
);
|
||||
|
||||
typedef _c_get_mempool_balance = ffi.Int64 Function(
|
||||
ffi.Uint8 coin,
|
||||
);
|
||||
|
||||
typedef _dart_get_mempool_balance = int Function(
|
||||
int coin,
|
||||
);
|
||||
|
||||
typedef _c_send_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
ffi.Pointer<ffi.Int8> recipients_json,
|
||||
ffi.Uint32 anchor_offset,
|
||||
|
@ -708,6 +811,7 @@ typedef _c_send_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
|||
);
|
||||
|
||||
typedef _dart_send_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
int account,
|
||||
ffi.Pointer<ffi.Int8> recipients_json,
|
||||
int anchor_offset,
|
||||
|
@ -715,65 +819,74 @@ typedef _dart_send_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
|||
int port,
|
||||
);
|
||||
|
||||
typedef _c_try_warp_sync = ffi.Int8 Function(
|
||||
ffi.Int8 get_tx,
|
||||
ffi.Uint32 anchor_offset,
|
||||
typedef _c_skip_to_last_height = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
);
|
||||
|
||||
typedef _dart_try_warp_sync = int Function(
|
||||
int get_tx,
|
||||
int anchor_offset,
|
||||
typedef _dart_skip_to_last_height = void Function(
|
||||
int coin,
|
||||
);
|
||||
|
||||
typedef _c_skip_to_last_height = ffi.Void Function();
|
||||
|
||||
typedef _dart_skip_to_last_height = void Function();
|
||||
|
||||
typedef _c_rewind_to_height = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 height,
|
||||
);
|
||||
|
||||
typedef _dart_rewind_to_height = void Function(
|
||||
int coin,
|
||||
int height,
|
||||
);
|
||||
|
||||
typedef _c_mempool_sync = ffi.Int64 Function();
|
||||
typedef _c_mempool_sync = ffi.Int64 Function(
|
||||
ffi.Uint8 coin,
|
||||
);
|
||||
|
||||
typedef _dart_mempool_sync = int Function();
|
||||
typedef _dart_mempool_sync = int Function(
|
||||
int coin,
|
||||
);
|
||||
|
||||
typedef _c_mempool_reset = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 height,
|
||||
);
|
||||
|
||||
typedef _dart_mempool_reset = void Function(
|
||||
int coin,
|
||||
int height,
|
||||
);
|
||||
|
||||
typedef _c_get_taddr_balance = ffi.Uint64 Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
);
|
||||
|
||||
typedef _dart_get_taddr_balance = int Function(
|
||||
int coin,
|
||||
int account,
|
||||
);
|
||||
|
||||
typedef _c_shield_taddr = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
);
|
||||
|
||||
typedef _dart_shield_taddr = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
int account,
|
||||
);
|
||||
|
||||
typedef _c_set_lwd_url = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> url,
|
||||
);
|
||||
|
||||
typedef _dart_set_lwd_url = void Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> url,
|
||||
);
|
||||
|
||||
typedef _c_prepare_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
ffi.Pointer<ffi.Int8> recipients_json,
|
||||
ffi.Int8 use_transparent,
|
||||
|
@ -781,6 +894,7 @@ typedef _c_prepare_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
|||
);
|
||||
|
||||
typedef _dart_prepare_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
int account,
|
||||
ffi.Pointer<ffi.Int8> recipients_json,
|
||||
int use_transparent,
|
||||
|
@ -788,28 +902,34 @@ typedef _dart_prepare_multi_payment = ffi.Pointer<ffi.Int8> Function(
|
|||
);
|
||||
|
||||
typedef _c_broadcast = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> tx_filename,
|
||||
);
|
||||
|
||||
typedef _dart_broadcast = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> tx_filename,
|
||||
);
|
||||
|
||||
typedef _c_broadcast_txhex = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> txhex,
|
||||
);
|
||||
|
||||
typedef _dart_broadcast_txhex = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> txhex,
|
||||
);
|
||||
|
||||
typedef _c_sync_historical_prices = ffi.Uint32 Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Int64 now,
|
||||
ffi.Uint32 days,
|
||||
ffi.Pointer<ffi.Int8> currency,
|
||||
);
|
||||
|
||||
typedef _dart_sync_historical_prices = int Function(
|
||||
int coin,
|
||||
int now,
|
||||
int days,
|
||||
ffi.Pointer<ffi.Int8> currency,
|
||||
|
@ -834,6 +954,7 @@ typedef _dart_get_sapling = ffi.Pointer<ffi.Int8> Function(
|
|||
);
|
||||
|
||||
typedef _c_store_contact = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 id,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
|
@ -841,6 +962,7 @@ typedef _c_store_contact = ffi.Void Function(
|
|||
);
|
||||
|
||||
typedef _dart_store_contact = void Function(
|
||||
int coin,
|
||||
int id,
|
||||
ffi.Pointer<ffi.Int8> name,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
|
@ -848,44 +970,56 @@ typedef _dart_store_contact = void Function(
|
|||
);
|
||||
|
||||
typedef _c_commit_unsaved_contacts = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
ffi.Uint32 anchor_offset,
|
||||
);
|
||||
|
||||
typedef _dart_commit_unsaved_contacts = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
int account,
|
||||
int anchor_offset,
|
||||
);
|
||||
|
||||
typedef _c_delete_account = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
);
|
||||
|
||||
typedef _dart_delete_account = void Function(
|
||||
int coin,
|
||||
int account,
|
||||
);
|
||||
|
||||
typedef _c_truncate_data = ffi.Void Function();
|
||||
typedef _c_truncate_data = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
);
|
||||
|
||||
typedef _dart_truncate_data = void Function();
|
||||
typedef _dart_truncate_data = void Function(
|
||||
int coin,
|
||||
);
|
||||
|
||||
typedef _c_make_payment_uri = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
ffi.Uint64 amount,
|
||||
ffi.Pointer<ffi.Int8> memo,
|
||||
);
|
||||
|
||||
typedef _dart_make_payment_uri = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> address,
|
||||
int amount,
|
||||
ffi.Pointer<ffi.Int8> memo,
|
||||
);
|
||||
|
||||
typedef _c_parse_payment_uri = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Pointer<ffi.Int8> uri,
|
||||
);
|
||||
|
||||
typedef _dart_parse_payment_uri = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
ffi.Pointer<ffi.Int8> uri,
|
||||
);
|
||||
|
||||
|
@ -912,20 +1046,24 @@ typedef _dart_restore_full_backup = ffi.Pointer<ffi.Int8> Function(
|
|||
);
|
||||
|
||||
typedef _c_store_share_secret = ffi.Void Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
ffi.Pointer<ffi.Int8> secret,
|
||||
);
|
||||
|
||||
typedef _dart_store_share_secret = void Function(
|
||||
int coin,
|
||||
int account,
|
||||
ffi.Pointer<ffi.Int8> secret,
|
||||
);
|
||||
|
||||
typedef _c_get_share_secret = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 account,
|
||||
);
|
||||
|
||||
typedef _dart_get_share_secret = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
int account,
|
||||
);
|
||||
|
||||
|
@ -974,12 +1112,14 @@ typedef _dart_run_multi_signer = int Function(
|
|||
);
|
||||
|
||||
typedef _c_split_account = ffi.Pointer<ffi.Int8> Function(
|
||||
ffi.Uint8 coin,
|
||||
ffi.Uint32 threshold,
|
||||
ffi.Uint32 participants,
|
||||
ffi.Uint32 account,
|
||||
);
|
||||
|
||||
typedef _dart_split_account = ffi.Pointer<ffi.Int8> Function(
|
||||
int coin,
|
||||
int threshold,
|
||||
int participants,
|
||||
int account,
|
||||
|
|
44
pubspec.lock
44
pubspec.lock
|
@ -301,7 +301,7 @@ packages:
|
|||
name: file_picker
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
version: "4.5.0"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -418,7 +418,7 @@ packages:
|
|||
name: flutter_native_splash
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
version: "1.3.2"
|
||||
flutter_palette:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -440,6 +440,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_speed_dial:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_speed_dial
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0+1"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -599,13 +606,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
material_design_icons_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -794,14 +794,14 @@ packages:
|
|||
name: path_provider_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
version: "2.0.12"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
version: "2.0.8"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -913,6 +913,20 @@ packages:
|
|||
name: quick_actions
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.0+10"
|
||||
quick_actions_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quick_actions_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.0+9"
|
||||
quick_actions_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quick_actions_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.0+9"
|
||||
quick_actions_platform_interface:
|
||||
dependency: transitive
|
||||
|
@ -1170,7 +1184,7 @@ packages:
|
|||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.8"
|
||||
version: "0.4.3"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1268,7 +1282,7 @@ packages:
|
|||
name: url_launcher_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
version: "2.0.6"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1347,5 +1361,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=2.15.1 <3.0.0"
|
||||
flutter: ">=2.10.0"
|
||||
dart: ">=2.15.0 <3.0.0"
|
||||
flutter: ">=2.8.0"
|
||||
|
|
11
pubspec.yaml
11
pubspec.yaml
|
@ -1,5 +1,5 @@
|
|||
name: warp
|
||||
description: A new Flutter project.
|
||||
name: ZYWallet
|
||||
description: Z/Ycash Wallet
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `pub publish`. This is preferred for private packages.
|
||||
|
@ -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.1.7+199
|
||||
version: 0.0.1+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
@ -63,6 +63,7 @@ dependencies:
|
|||
git:
|
||||
url: https://github.com/hhanh00/flutter_barcode_scanner.git
|
||||
ref: d338bdc5c0b797d81694f04bd5f52e8171939af5
|
||||
flutter_speed_dial: ^5.0.0
|
||||
currency_text_input_formatter: ^2.1.2
|
||||
sensors_plus: ^1.1.0
|
||||
connectivity_plus: ^1.1.0
|
||||
|
@ -89,7 +90,7 @@ dev_dependencies:
|
|||
flutter_native_splash: ^1.2.3
|
||||
|
||||
flutter_app_name:
|
||||
name: "YWallet"
|
||||
name: "ZYWallet"
|
||||
|
||||
flutter_icons:
|
||||
android: true
|
||||
|
@ -120,6 +121,8 @@ flutter:
|
|||
- assets/wallet.svg
|
||||
- assets/contacts.svg
|
||||
- assets/multipay.svg
|
||||
- assets/ycash.png
|
||||
- assets/zcash.png
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
|
150
pubspec.yaml.tpl
150
pubspec.yaml.tpl
|
@ -1,150 +0,0 @@
|
|||
name: warp
|
||||
description: A new Flutter project.
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
# followed by an optional build number separated by a +.
|
||||
# Both the version and the builder number may be overridden in flutter
|
||||
# build by specifying --build-name and --build-number, respectively.
|
||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||
# 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.1.8+202
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
warp_api:
|
||||
path: packages/warp_api_ffi
|
||||
sqflite: ^2.0.0+4
|
||||
flutter_mobx: ^2.0.2
|
||||
qr_flutter: ^4.0.0
|
||||
http: ^0.13.3
|
||||
intl: ^0.17.0
|
||||
path: ^1.8.0
|
||||
material_design_icons_flutter: ^5.0.5955-rc.1
|
||||
rflutter_alert: ^2.0.4
|
||||
sprintf: ^6.0.0
|
||||
local_auth: ^1.1.7
|
||||
shared_preferences: ^2.0.7
|
||||
flutter_markdown: ^0.6.6
|
||||
package_info_plus: ^1.0.6
|
||||
velocity_x: ^3.3.0
|
||||
decimal: ^1.3.0
|
||||
flutter_form_builder: ^6.1.0+1
|
||||
url_launcher: ^6.0.10
|
||||
flex_color_scheme: ^3.0.1
|
||||
flutter_colorpicker: ^0.6.0
|
||||
fl_chart: ^0.40.0
|
||||
k_chart:
|
||||
git:
|
||||
url: https://github.com/hhanh00/k_chart.git
|
||||
ref: 821f81681f8ee819cd721498f7b28d290f4ebe38
|
||||
grouped_list: ^4.1.0
|
||||
json_annotation: ^4.1.0
|
||||
share_plus: ^2.1.4
|
||||
path_provider: ^2.0.3
|
||||
file_picker: ^4.0.2
|
||||
mustache_template: ^2.0.0
|
||||
rate_my_app: ^1.1.1
|
||||
flutter_palette: ^1.1.0+1
|
||||
flutter_svg: ^0.22.0
|
||||
flutter_typeahead: ^3.2.0
|
||||
flutter_barcode_scanner:
|
||||
git:
|
||||
url: https://github.com/hhanh00/flutter_barcode_scanner.git
|
||||
ref: d338bdc5c0b797d81694f04bd5f52e8171939af5
|
||||
currency_text_input_formatter: ^2.1.2
|
||||
sensors_plus: ^1.1.0
|
||||
connectivity_plus: ^1.1.0
|
||||
uni_links: ^0.5.1
|
||||
quick_actions: ^0.6.0
|
||||
csv: ^5.0.0
|
||||
network_info_plus : ^2.0.2
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.1.2
|
||||
mobx_codegen: ^2.0.3
|
||||
json_serializable: ^5.0.0
|
||||
flutter_launcher_icons: any
|
||||
flutter_app_name: any
|
||||
change_app_package_name: any
|
||||
flutter_native_splash: ^1.2.3
|
||||
|
||||
flutter_app_name:
|
||||
name: "{{APP_TITLE}}"
|
||||
|
||||
flutter_icons:
|
||||
android: true
|
||||
ios: true
|
||||
remove_alpha_ios: true
|
||||
image_path: "assets/icon.png"
|
||||
|
||||
flutter_native_splash:
|
||||
color_dark: "#FFFFFF"
|
||||
color: "#000000"
|
||||
image: "assets/icon.png"
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/icon.png
|
||||
- assets/about.md
|
||||
- assets/wallet.svg
|
||||
- assets/contacts.svg
|
||||
- assets/multipay.svg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
flutter_intl:
|
||||
enabled: true
|
Loading…
Reference in New Issue