Unification YCash/ZCash

This commit is contained in:
Hanh 2022-03-07 22:53:18 +08:00
parent 439a552d30
commit 66b1face96
55 changed files with 2833 additions and 5757 deletions

4
.gitignore vendored
View File

@ -54,5 +54,5 @@ jniLibs/
ic_launcher.png
Icon-App-*.png
*.apk
assets/icon.png
lib/coin/coindef.dart
Cargo.lock

3409
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,7 @@ android {
}
defaultConfig {
applicationId "me.hanh.zwallet"
applicationId "me.hanh.zywallet"
minSdkVersion 24
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()

View File

@ -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.
-->

View File

@ -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" />

View File

@ -1,4 +1,4 @@
package me.hanh.zwallet
package me.hanh.zywallet
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine

View File

@ -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.
-->

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -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

5
install-aab.sh Executable file
View File

@ -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

View File

@ -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,

View File

@ -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 ? '-' : '+';

View File

@ -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');
}

345
lib/accounts.dart Normal file
View File

@ -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);
}

View File

@ -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');
}
}

View File

@ -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)
])));
}
}

View File

@ -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);
}
}

7
lib/coin/coins.dart Normal file
View File

@ -0,0 +1,7 @@
import 'coin.dart';
import 'ycash.dart';
import 'zcash.dart';
CoinBase ycash = YcashCoin();
CoinBase zcash = ZcashCoin();

View File

@ -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];
}

View File

@ -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"),

View File

@ -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;
}

228
lib/db.dart Normal file
View File

@ -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);
}

View File

@ -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;

View File

@ -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"),

View File

@ -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"),

View File

@ -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"),

View File

@ -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> {

View File

@ -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;

263
lib/home.dart Normal file
View File

@ -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);
}
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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é"
}

View File

@ -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);
})),

View File

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

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -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;
});
}

View File

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

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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';

View File

@ -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}");
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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, &currency)
api::sync_historical_prices(coin, now, days, &currency)
}
#[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

View File

@ -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);
}

View File

@ -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,

View File

@ -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"

View File

@ -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.

View File

@ -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