2021-06-26 07:30:12 -07:00
|
|
|
import 'dart:async';
|
2021-09-11 02:35:48 -07:00
|
|
|
import 'dart:ui';
|
2021-09-13 02:37:35 -07:00
|
|
|
import 'dart:math';
|
2021-06-26 07:30:12 -07:00
|
|
|
|
2021-08-05 06:43:05 -07:00
|
|
|
import 'package:file_picker/file_picker.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/rendering.dart';
|
2021-07-10 22:20:53 -07:00
|
|
|
import 'package:flutter/services.dart';
|
2021-08-09 07:13:42 -07:00
|
|
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
2021-08-09 07:13:42 -07:00
|
|
|
import 'package:intl/intl.dart';
|
2021-08-27 19:10:33 -07:00
|
|
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:qr_flutter/qr_flutter.dart';
|
2021-09-13 02:37:35 -07:00
|
|
|
import 'package:sensors_plus/sensors_plus.dart';
|
2021-09-25 02:09:41 -07:00
|
|
|
import 'package:warp/payment_uri.dart';
|
2021-07-07 08:40:05 -07:00
|
|
|
import 'package:warp/store.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:warp_api/warp_api.dart';
|
|
|
|
|
|
|
|
import 'about.dart';
|
2021-09-11 18:16:53 -07:00
|
|
|
import 'budget.dart';
|
2021-08-16 06:07:16 -07:00
|
|
|
import 'chart.dart';
|
2021-09-08 07:14:19 -07:00
|
|
|
import 'contact.dart';
|
2021-09-15 19:40:11 -07:00
|
|
|
import 'history.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'main.dart';
|
2021-08-15 09:18:09 -07:00
|
|
|
import 'generated/l10n.dart';
|
2021-09-17 18:14:08 -07:00
|
|
|
import 'note.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
class AccountPage extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => _AccountPageState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _AccountPageState extends State<AccountPage>
|
2021-07-10 22:20:53 -07:00
|
|
|
with
|
|
|
|
WidgetsBindingObserver,
|
|
|
|
AutomaticKeepAliveClientMixin,
|
|
|
|
SingleTickerProviderStateMixin {
|
2021-09-10 02:56:15 -07:00
|
|
|
Timer? _timerSync;
|
2021-07-07 08:40:05 -07:00
|
|
|
int _progress = 0;
|
2021-07-07 21:22:54 -07:00
|
|
|
bool _useSnapAddress = false;
|
|
|
|
String _snapAddress = "";
|
2021-09-10 02:56:15 -07:00
|
|
|
late TabController _tabController;
|
2021-07-10 22:20:53 -07:00
|
|
|
bool _accountTab = true;
|
2021-09-08 07:14:19 -07:00
|
|
|
bool _contactsTab = false;
|
2021-09-10 02:56:15 -07:00
|
|
|
StreamSubscription? _progressDispose;
|
|
|
|
StreamSubscription? _syncDispose;
|
2021-09-13 02:37:35 -07:00
|
|
|
StreamSubscription? _accDispose;
|
2021-09-08 07:14:19 -07:00
|
|
|
final contactKey = GlobalKey<ContactsState>();
|
2021-09-13 02:37:35 -07:00
|
|
|
bool _flat = false;
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
bool get wantKeepAlive => true;
|
|
|
|
|
|
|
|
@override
|
|
|
|
initState() {
|
2021-07-07 08:40:05 -07:00
|
|
|
super.initState();
|
2021-08-09 07:13:42 -07:00
|
|
|
_tabController = TabController(length: 6, vsync: this);
|
2021-07-10 22:20:53 -07:00
|
|
|
_tabController.addListener(() {
|
|
|
|
setState(() {
|
|
|
|
_accountTab = _tabController.index == 0;
|
2021-09-08 07:14:19 -07:00
|
|
|
_contactsTab = _tabController.index == 5;
|
2021-07-10 22:20:53 -07:00
|
|
|
});
|
|
|
|
});
|
2021-06-26 07:30:12 -07:00
|
|
|
Future.microtask(() async {
|
2021-07-07 08:40:05 -07:00
|
|
|
await accountManager.updateUnconfirmedBalance();
|
2021-09-05 06:06:54 -07:00
|
|
|
await accountManager.fetchAccountData(false);
|
2021-09-08 07:14:19 -07:00
|
|
|
await contacts.fetchContacts();
|
2021-07-19 01:17:23 -07:00
|
|
|
await _setupTimer();
|
2021-06-26 07:30:12 -07:00
|
|
|
});
|
2021-09-10 02:56:15 -07:00
|
|
|
WidgetsBinding.instance?.addObserver(this);
|
2021-08-31 08:02:56 -07:00
|
|
|
_progressDispose = progressStream.listen((percent) {
|
2021-07-07 08:40:05 -07:00
|
|
|
setState(() {
|
|
|
|
_progress = percent;
|
|
|
|
});
|
|
|
|
});
|
2021-08-31 08:02:56 -07:00
|
|
|
_syncDispose = syncStream.listen((height) {
|
|
|
|
setState(() {
|
2021-09-05 06:06:54 -07:00
|
|
|
if (height >= 0) {
|
2021-08-31 08:02:56 -07:00
|
|
|
syncStatus.setSyncHeight(height);
|
2021-09-05 06:06:54 -07:00
|
|
|
eta.checkpoint(height, DateTime.now());
|
|
|
|
} else {
|
2021-08-31 08:02:56 -07:00
|
|
|
WarpApi.mempoolReset(syncStatus.latestHeight);
|
|
|
|
_trySync();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2021-09-13 19:02:20 -07:00
|
|
|
_accDispose = accelerometerEvents.listen(_handleAccel);
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_timerSync?.cancel();
|
2021-09-10 02:56:15 -07:00
|
|
|
WidgetsBinding.instance?.removeObserver(this);
|
|
|
|
_progressDispose?.cancel();
|
|
|
|
_syncDispose?.cancel();
|
2021-09-13 02:37:35 -07:00
|
|
|
_accDispose?.cancel();
|
2021-06-26 07:30:12 -07:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
|
|
switch (state) {
|
|
|
|
case AppLifecycleState.detached:
|
|
|
|
case AppLifecycleState.inactive:
|
|
|
|
case AppLifecycleState.paused:
|
|
|
|
_timerSync?.cancel();
|
2021-09-13 19:02:20 -07:00
|
|
|
_timerSync = null;
|
|
|
|
_accDispose?.cancel();
|
|
|
|
_accDispose = null;
|
2021-06-26 07:30:12 -07:00
|
|
|
break;
|
|
|
|
case AppLifecycleState.resumed:
|
2021-09-15 07:42:44 -07:00
|
|
|
if (_timerSync == null) _setupTimer();
|
2021-09-13 19:02:20 -07:00
|
|
|
if (_accDispose == null)
|
|
|
|
_accDispose = accelerometerEvents.listen(_handleAccel);
|
2021-06-26 07:30:12 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
super.build(context);
|
2021-09-05 21:41:32 -07:00
|
|
|
if (!syncStatus.isSynced() && !syncStatus.syncing) _trySync();
|
2021-07-12 04:32:49 -07:00
|
|
|
final theme = Theme.of(this.context);
|
2021-08-07 00:32:10 -07:00
|
|
|
final hasTAddr = accountManager.taddress.isNotEmpty;
|
2021-09-09 03:09:14 -07:00
|
|
|
final qrSize = getScreenSize(context) / 2.5;
|
2021-09-05 21:41:32 -07:00
|
|
|
|
2021-07-10 22:20:53 -07:00
|
|
|
return Scaffold(
|
2021-08-09 07:13:42 -07:00
|
|
|
appBar: AppBar(
|
2021-09-08 19:56:24 -07:00
|
|
|
centerTitle: true,
|
2021-08-09 07:13:42 -07:00
|
|
|
title: Observer(
|
2021-09-08 19:56:24 -07:00
|
|
|
builder: (context) => Text("${accountManager.active.name}")),
|
2021-08-09 07:13:42 -07:00
|
|
|
bottom: TabBar(controller: _tabController, isScrollable: true, tabs: [
|
2021-08-15 09:18:09 -07:00
|
|
|
Tab(text: S.of(context).account),
|
|
|
|
Tab(text: S.of(context).notes),
|
|
|
|
Tab(text: S.of(context).history),
|
|
|
|
Tab(text: S.of(context).budget),
|
|
|
|
Tab(text: S.of(context).tradingPl),
|
|
|
|
Tab(text: S.of(context).contacts),
|
2021-07-10 22:20:53 -07:00
|
|
|
]),
|
2021-08-09 07:13:42 -07:00
|
|
|
actions: [
|
|
|
|
Observer(builder: (context) {
|
|
|
|
accountManager.canPay;
|
|
|
|
return PopupMenuButton<String>(
|
|
|
|
itemBuilder: (context) => [
|
2021-08-23 19:04:45 -07:00
|
|
|
PopupMenuItem(
|
|
|
|
child: Text(S.of(context).accounts), value: "Accounts"),
|
|
|
|
PopupMenuItem(
|
|
|
|
child: Text(S.of(context).backup), value: "Backup"),
|
|
|
|
PopupMenuItem(
|
|
|
|
child: Text(S.of(context).rescan), value: "Rescan"),
|
2021-08-09 07:13:42 -07:00
|
|
|
if (accountManager.canPay)
|
2021-08-23 19:04:45 -07:00
|
|
|
PopupMenuItem(
|
|
|
|
child: Text(S.of(context).coldStorage), value: "Cold"),
|
2021-08-09 07:13:42 -07:00
|
|
|
if (accountManager.canPay)
|
2021-08-23 19:04:45 -07:00
|
|
|
PopupMenuItem(
|
|
|
|
child: Text(S.of(context).multipay), value: "MultiPay"),
|
|
|
|
PopupMenuItem(
|
|
|
|
child: Text(S.of(context).broadcast), value: "Broadcast"),
|
|
|
|
PopupMenuItem(
|
|
|
|
child: Text(S.of(context).settings), value: "Settings"),
|
2021-08-15 09:18:09 -07:00
|
|
|
PopupMenuItem(child: Text(S.of(context).about), value: "About"),
|
2021-08-09 07:13:42 -07:00
|
|
|
],
|
|
|
|
onSelected: _onMenu,
|
|
|
|
);
|
|
|
|
})
|
|
|
|
],
|
|
|
|
),
|
|
|
|
body: TabBarView(controller: _tabController, children: [
|
|
|
|
SingleChildScrollView(
|
|
|
|
padding: EdgeInsets.all(20),
|
|
|
|
child: Center(
|
|
|
|
child: Column(children: [
|
2021-09-05 06:06:54 -07:00
|
|
|
Observer(builder: (context) {
|
|
|
|
final _1 = eta.eta;
|
|
|
|
final _2 = syncStatus.syncedHeight;
|
|
|
|
final _3 = syncStatus.latestHeight;
|
2021-09-05 21:41:32 -07:00
|
|
|
return syncStatus.syncedHeight < 0
|
|
|
|
? Text("")
|
2021-09-05 06:06:54 -07:00
|
|
|
: syncStatus.isSynced()
|
|
|
|
? Text('${syncStatus.syncedHeight}',
|
|
|
|
style: theme.textTheme.caption)
|
|
|
|
: Text(
|
|
|
|
'${syncStatus.syncedHeight} / ${syncStatus.latestHeight} ${eta.eta}',
|
2021-09-10 02:56:15 -07:00
|
|
|
style: theme.textTheme.caption!
|
2021-09-05 06:06:54 -07:00
|
|
|
.apply(color: theme.primaryColor));
|
|
|
|
}),
|
2021-08-09 07:13:42 -07:00
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
|
|
|
Observer(builder: (context) {
|
|
|
|
final _ = accountManager.active.address;
|
|
|
|
final address = _address();
|
2021-09-11 18:16:53 -07:00
|
|
|
final shortAddress = addressLeftTrim(address);
|
2021-08-13 20:44:53 -07:00
|
|
|
final showTAddr = accountManager.showTAddr;
|
2021-09-13 02:37:35 -07:00
|
|
|
final hide = settings.autoHide && _flat;
|
2021-08-09 07:13:42 -07:00
|
|
|
return Column(children: [
|
|
|
|
if (hasTAddr)
|
|
|
|
Text(showTAddr
|
2021-08-15 09:18:09 -07:00
|
|
|
? S.of(context).tapQrCodeForShieldedAddress
|
|
|
|
: S.of(context).tapQrCodeForTransparentAddress),
|
2021-08-09 07:13:42 -07:00
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
|
|
|
GestureDetector(
|
|
|
|
onTap: hasTAddr ? _onQRTap : null,
|
2021-09-15 07:42:44 -07:00
|
|
|
child: RotatedBox(
|
|
|
|
quarterTurns: hide ? 2 : 0,
|
|
|
|
child: QrImage(
|
|
|
|
data: address,
|
|
|
|
size: qrSize,
|
|
|
|
embeddedImage: AssetImage('assets/icon.png'),
|
|
|
|
backgroundColor: Colors.white))),
|
2021-08-09 07:13:42 -07:00
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
|
|
|
RichText(
|
|
|
|
text: TextSpan(children: [
|
|
|
|
TextSpan(
|
2021-09-15 07:42:44 -07:00
|
|
|
text: '$shortAddress ',
|
|
|
|
style: theme.textTheme.bodyText2),
|
2021-08-09 07:13:42 -07:00
|
|
|
WidgetSpan(
|
|
|
|
child: GestureDetector(
|
|
|
|
child: Icon(Icons.content_copy),
|
|
|
|
onTap: _onAddressCopy)),
|
2021-08-27 19:10:33 -07:00
|
|
|
WidgetSpan(
|
2021-09-05 02:48:51 -07:00
|
|
|
child: Padding(
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 4))),
|
2021-08-27 19:10:33 -07:00
|
|
|
WidgetSpan(
|
|
|
|
child: GestureDetector(
|
|
|
|
child: Icon(MdiIcons.qrcodeScan),
|
|
|
|
onTap: _onReceive)),
|
2021-08-09 07:13:42 -07:00
|
|
|
])),
|
2021-08-13 20:44:53 -07:00
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
2021-08-09 07:13:42 -07:00
|
|
|
if (!showTAddr)
|
2021-08-13 20:44:53 -07:00
|
|
|
OutlinedButton(
|
2021-08-15 09:18:09 -07:00
|
|
|
child: Text(S.of(context).newSnapAddress),
|
2021-08-13 20:44:53 -07:00
|
|
|
style: OutlinedButton.styleFrom(
|
|
|
|
side: BorderSide(
|
|
|
|
width: 1, color: theme.primaryColor)),
|
2021-08-09 07:13:42 -07:00
|
|
|
onPressed: _onSnapAddress),
|
|
|
|
if (showTAddr)
|
2021-08-13 20:44:53 -07:00
|
|
|
OutlinedButton(
|
2021-08-15 09:18:09 -07:00
|
|
|
child: Text(S.of(context).shieldTranspBalance),
|
2021-08-13 20:44:53 -07:00
|
|
|
style: OutlinedButton.styleFrom(
|
|
|
|
side:
|
|
|
|
BorderSide(width: 1, color: theme.primaryColor)),
|
2021-09-26 11:44:19 -07:00
|
|
|
onPressed: () { shieldTAddr(context); },
|
2021-08-09 07:13:42 -07:00
|
|
|
)
|
|
|
|
]);
|
|
|
|
}),
|
|
|
|
Observer(builder: (context) {
|
2021-09-15 22:55:20 -07:00
|
|
|
final showTAddr = accountManager.showTAddr;
|
|
|
|
final balance = showTAddr
|
2021-08-09 07:13:42 -07:00
|
|
|
? accountManager.tbalance
|
|
|
|
: accountManager.balance;
|
2021-09-13 02:37:35 -07:00
|
|
|
final hide = settings.autoHide && _flat;
|
2021-09-23 01:37:43 -07:00
|
|
|
final balanceColor = !showTAddr
|
|
|
|
? theme.colorScheme.primaryVariant
|
|
|
|
: theme.colorScheme.secondaryVariant;
|
2021-09-13 02:37:35 -07:00
|
|
|
final balanceHi = hide ? '-------' : _getBalance_hi(balance);
|
|
|
|
final deviceWidth = getWidth(context);
|
|
|
|
final digits = deviceWidth.index < DeviceWidth.sm.index ? 7 : 9;
|
2021-09-15 07:42:44 -07:00
|
|
|
final balanceStyle = (balanceHi.length > digits
|
|
|
|
? theme.textTheme.headline4
|
|
|
|
: theme.textTheme.headline2)!
|
2021-09-15 22:55:20 -07:00
|
|
|
.copyWith(color: balanceColor);
|
2021-08-09 07:13:42 -07:00
|
|
|
return Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
|
|
textBaseline: TextBaseline.ideographic,
|
|
|
|
children: <Widget>[
|
2021-09-15 07:42:44 -07:00
|
|
|
if (!hide)
|
|
|
|
Text('${coin.symbol}',
|
|
|
|
style: theme.textTheme.headline5),
|
|
|
|
Text(' $balanceHi', style: balanceStyle),
|
2021-09-13 02:37:35 -07:00
|
|
|
if (!hide) Text('${_getBalance_lo(balance)}'),
|
2021-08-09 07:13:42 -07:00
|
|
|
]);
|
|
|
|
}),
|
|
|
|
Observer(builder: (context) {
|
2021-09-13 02:37:35 -07:00
|
|
|
final hide = settings.autoHide && _flat;
|
2021-08-13 20:44:53 -07:00
|
|
|
final balance = accountManager.showTAddr
|
2021-08-09 07:13:42 -07:00
|
|
|
? accountManager.tbalance
|
|
|
|
: accountManager.balance;
|
|
|
|
final fx = _fx();
|
|
|
|
final balanceFX = balance * fx / ZECUNIT;
|
2021-09-15 07:42:44 -07:00
|
|
|
return hide
|
|
|
|
? Text(S.of(context).tiltYourDeviceUpToRevealYourBalance)
|
|
|
|
: Column(children: [
|
|
|
|
if (fx != 0.0)
|
|
|
|
Text(
|
2021-09-21 05:52:52 -07:00
|
|
|
"${decimalFormat(balanceFX, 2, symbol: settings.currency)}",
|
2021-09-15 07:42:44 -07:00
|
|
|
style: theme.textTheme.headline6),
|
|
|
|
if (fx != 0.0)
|
|
|
|
Text(
|
2021-09-21 05:52:52 -07:00
|
|
|
"1 ${coin.ticker} = ${decimalFormat(fx, 2, symbol: settings.currency)}"),
|
2021-09-15 07:42:44 -07:00
|
|
|
]);
|
2021-08-09 07:13:42 -07:00
|
|
|
}),
|
|
|
|
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),
|
|
|
|
]))),
|
|
|
|
NoteWidget(tabTo),
|
|
|
|
HistoryWidget(tabTo),
|
|
|
|
BudgetWidget(),
|
|
|
|
PnLWidget(),
|
2021-09-08 07:14:19 -07:00
|
|
|
ContactsTab(key: contactKey),
|
2021-08-09 07:13:42 -07:00
|
|
|
]),
|
|
|
|
floatingActionButton: _accountTab
|
|
|
|
? FloatingActionButton(
|
|
|
|
onPressed: _onSend,
|
|
|
|
backgroundColor: Theme.of(context)
|
|
|
|
.accentColor
|
|
|
|
.withOpacity(accountManager.canPay ? 1.0 : 0.3),
|
|
|
|
child: Icon(Icons.send),
|
2021-09-08 08:06:35 -07:00
|
|
|
)
|
|
|
|
: _contactsTab
|
|
|
|
? FloatingActionButton(
|
|
|
|
onPressed: _onAddContact,
|
|
|
|
backgroundColor: Theme.of(context).accentColor,
|
|
|
|
child: Icon(Icons.add),
|
|
|
|
)
|
|
|
|
: Container(), // This trailing comma makes auto-formatting nicer for build methods.
|
2021-08-09 07:13:42 -07:00
|
|
|
);
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-07-12 04:32:49 -07:00
|
|
|
void tabTo(int index) {
|
|
|
|
if (index != _tabController.index) _tabController.animateTo(index);
|
|
|
|
}
|
|
|
|
|
2021-09-09 03:09:14 -07:00
|
|
|
String _address() {
|
|
|
|
final address = accountManager.showTAddr
|
|
|
|
? accountManager.taddress
|
|
|
|
: (_useSnapAddress
|
2021-09-15 07:42:44 -07:00
|
|
|
? _uaAddress(_snapAddress, accountManager.taddress, settings.useUA)
|
|
|
|
: _uaAddress(accountManager.active.address, accountManager.taddress,
|
|
|
|
settings.useUA));
|
2021-09-09 08:23:10 -07:00
|
|
|
return address;
|
2021-09-09 03:09:14 -07:00
|
|
|
}
|
2021-08-27 02:51:34 -07:00
|
|
|
|
|
|
|
String _uaAddress(String zaddress, String taddress, bool useUA) =>
|
|
|
|
useUA ? WarpApi.getUA(zaddress, taddress) : zaddress;
|
2021-07-10 22:20:53 -07:00
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
_sign(int b) {
|
|
|
|
return b < 0 ? '-' : '+';
|
|
|
|
}
|
|
|
|
|
2021-07-09 06:33:39 -07:00
|
|
|
_onQRTap() {
|
2021-08-07 00:32:10 -07:00
|
|
|
accountManager.toggleShowTAddr();
|
2021-07-09 06:33:39 -07:00
|
|
|
}
|
|
|
|
|
2021-07-10 22:20:53 -07:00
|
|
|
_onAddressCopy() {
|
|
|
|
Clipboard.setData(ClipboardData(text: _address()));
|
2021-08-23 19:04:45 -07:00
|
|
|
final snackBar =
|
|
|
|
SnackBar(content: Text(S.of(context).addressCopiedToClipboard));
|
2021-09-10 02:56:15 -07:00
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
2021-07-10 22:20:53 -07:00
|
|
|
}
|
|
|
|
|
2021-09-25 02:09:41 -07:00
|
|
|
_onReceive() async {
|
2021-09-25 08:03:05 -07:00
|
|
|
await showDialog(context: context,
|
|
|
|
barrierColor: Colors.black,
|
|
|
|
builder: (context) =>
|
|
|
|
Dialog(child: PaymentURIPage(_address())));
|
2021-08-27 19:10:33 -07:00
|
|
|
}
|
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
_unconfirmedStyle() {
|
2021-09-15 07:42:44 -07:00
|
|
|
return TextStyle(
|
|
|
|
color: amountColor(context, accountManager.unconfirmedBalance));
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
_getBalance_hi(int b) {
|
2021-09-21 05:52:52 -07:00
|
|
|
return decimalFormat((b.abs() ~/ 100000) / 1000.0, 3);
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
_getBalance_lo(b) {
|
|
|
|
return (b.abs() % 100000).toString().padLeft(5, '0');
|
|
|
|
}
|
|
|
|
|
2021-07-19 01:17:23 -07:00
|
|
|
_setupTimer() async {
|
|
|
|
await _sync();
|
2021-06-26 07:30:12 -07:00
|
|
|
_timerSync = Timer.periodic(Duration(seconds: 15), (Timer t) {
|
|
|
|
_trySync();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-08-07 00:32:10 -07:00
|
|
|
double _fx() {
|
|
|
|
return priceStore.zecPrice;
|
|
|
|
}
|
|
|
|
|
2021-09-08 08:06:35 -07:00
|
|
|
_sync() async {}
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
_trySync() async {
|
|
|
|
priceStore.fetchZecPrice();
|
|
|
|
if (syncStatus.syncedHeight < 0) return;
|
|
|
|
await syncStatus.update();
|
|
|
|
await accountManager.updateUnconfirmedBalance();
|
|
|
|
if (!syncStatus.isSynced()) {
|
2021-07-09 22:44:34 -07:00
|
|
|
final res =
|
|
|
|
await WarpApi.tryWarpSync(settings.getTx, settings.anchorOffset);
|
2021-06-26 07:30:12 -07:00
|
|
|
if (res == 1) {
|
|
|
|
// Reorg
|
|
|
|
final targetHeight = syncStatus.syncedHeight - 10;
|
|
|
|
WarpApi.rewindToHeight(targetHeight);
|
|
|
|
syncStatus.setSyncHeight(targetHeight);
|
|
|
|
} else if (res == 0) {
|
2021-07-19 01:17:23 -07:00
|
|
|
syncStatus.update();
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
}
|
2021-09-05 06:06:54 -07:00
|
|
|
await accountManager.fetchAccountData(false);
|
2021-07-07 08:40:05 -07:00
|
|
|
await accountManager.updateBalance();
|
2021-09-26 22:31:55 -07:00
|
|
|
await accountManager.updateTBalance();
|
2021-06-26 07:30:12 -07:00
|
|
|
await accountManager.updateUnconfirmedBalance();
|
2021-09-08 07:14:19 -07:00
|
|
|
await contacts.fetchContacts();
|
2021-09-26 22:31:55 -07:00
|
|
|
accountManager.autoshield();
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-07-07 21:22:54 -07:00
|
|
|
_onSnapAddress() {
|
|
|
|
final address = accountManager.newAddress();
|
|
|
|
setState(() {
|
|
|
|
_useSnapAddress = true;
|
|
|
|
_snapAddress = address;
|
|
|
|
});
|
|
|
|
Timer(Duration(seconds: 15), () {
|
|
|
|
setState(() {
|
|
|
|
_useSnapAddress = false;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-07-09 06:33:39 -07:00
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
_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;
|
2021-07-07 08:40:05 -07:00
|
|
|
case "Cold":
|
|
|
|
_cold();
|
|
|
|
break;
|
2021-08-02 22:58:02 -07:00
|
|
|
case "MultiPay":
|
|
|
|
_multiPay();
|
|
|
|
break;
|
2021-08-05 06:43:05 -07:00
|
|
|
case "Broadcast":
|
|
|
|
_broadcast();
|
|
|
|
break;
|
2021-07-09 06:33:39 -07:00
|
|
|
case "Settings":
|
|
|
|
_settings();
|
|
|
|
break;
|
2021-06-26 07:30:12 -07:00
|
|
|
case "About":
|
|
|
|
showAbout(this.context);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_backup() async {
|
2021-09-25 18:15:14 -07:00
|
|
|
final didAuthenticate = await authenticate(context, S.of(context).pleaseAuthenticateToShowAccountSeed);
|
2021-09-25 17:31:39 -07:00
|
|
|
if (didAuthenticate) {
|
|
|
|
Navigator.of(context).pushNamed('/backup');
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_rescan() {
|
2021-09-05 20:13:08 -07:00
|
|
|
rescanDialog(context, () {
|
2021-09-08 08:06:35 -07:00
|
|
|
Navigator.of(context).pop();
|
|
|
|
syncStatus.sync(context);
|
2021-09-05 21:41:32 -07:00
|
|
|
});
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
2021-07-07 08:40:05 -07:00
|
|
|
|
|
|
|
_cold() {
|
|
|
|
showDialog(
|
2021-07-30 05:36:46 -07:00
|
|
|
context: context,
|
2021-07-07 08:40:05 -07:00
|
|
|
barrierDismissible: false,
|
|
|
|
builder: (context) => AlertDialog(
|
2021-08-23 19:04:45 -07:00
|
|
|
title: Text(S.of(context).coldStorage),
|
|
|
|
content:
|
|
|
|
Text(S.of(context).doYouWantToDeleteTheSecretKeyAndConvert),
|
|
|
|
actions: confirmButtons(context, _convertToWatchOnly,
|
|
|
|
okLabel: S.of(context).delete)));
|
2021-07-07 08:40:05 -07:00
|
|
|
}
|
|
|
|
|
2021-08-02 22:58:02 -07:00
|
|
|
_multiPay() {
|
|
|
|
Navigator.of(context).pushNamed('/multipay');
|
|
|
|
}
|
|
|
|
|
2021-08-05 06:43:05 -07:00
|
|
|
_broadcast() async {
|
|
|
|
final result = await FilePicker.platform.pickFiles();
|
|
|
|
|
|
|
|
if (result != null) {
|
|
|
|
final res = WarpApi.broadcast(result.files.single.path);
|
|
|
|
final snackBar = SnackBar(content: Text(res));
|
2021-09-10 02:56:15 -07:00
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
2021-08-05 06:43:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-07 08:40:05 -07:00
|
|
|
_convertToWatchOnly() {
|
|
|
|
accountManager.convertToWatchOnly();
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
}
|
2021-07-09 06:33:39 -07:00
|
|
|
|
|
|
|
_settings() {
|
2021-09-08 07:14:19 -07:00
|
|
|
Navigator.of(context).pushNamed('/settings');
|
|
|
|
}
|
|
|
|
|
|
|
|
_onAddContact() async {
|
2021-09-15 07:42:44 -07:00
|
|
|
final contact = await contactKey.currentState
|
|
|
|
?.showContactForm(context, Contact.empty());
|
2021-09-08 07:14:19 -07:00
|
|
|
if (contact != null) {
|
|
|
|
contacts.add(contact);
|
|
|
|
}
|
2021-07-09 06:33:39 -07:00
|
|
|
}
|
2021-09-13 19:02:20 -07:00
|
|
|
|
2021-09-15 02:28:07 -07:00
|
|
|
_handleAccel(AccelerometerEvent event) {
|
2021-09-15 07:42:44 -07:00
|
|
|
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;
|
2021-09-15 02:28:07 -07:00
|
|
|
final flat = inclination < 20;
|
2021-09-13 19:02:20 -07:00
|
|
|
if (flat != _flat)
|
|
|
|
setState(() {
|
|
|
|
_flat = flat;
|
|
|
|
});
|
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-07-12 04:32:49 -07:00
|
|
|
class BudgetWidget extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => BudgetState();
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2021-07-18 08:59:02 -07:00
|
|
|
final _ = accountManager.dataEpoch;
|
2021-07-12 04:32:49 -07:00
|
|
|
return Column(
|
|
|
|
children: [
|
2021-09-08 08:06:35 -07:00
|
|
|
Card(
|
2021-09-15 07:42:44 -07:00
|
|
|
child: Column(children: [
|
2021-08-15 09:18:09 -07:00
|
|
|
Text(S.of(context).largestSpendingsByAddress,
|
2021-07-12 04:32:49 -07:00
|
|
|
style: Theme.of(context).textTheme.headline6),
|
2021-09-11 18:16:53 -07:00
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
|
|
|
BudgetChart(),
|
2021-09-08 08:06:35 -07:00
|
|
|
])),
|
2021-07-12 04:32:49 -07:00
|
|
|
Expanded(
|
|
|
|
child: Card(
|
|
|
|
child: Column(children: [
|
2021-08-15 09:18:09 -07:00
|
|
|
Text(S.of(context).accountBalanceHistory,
|
2021-07-12 04:32:49 -07:00
|
|
|
style: Theme.of(context).textTheme.headline6),
|
2021-09-11 18:16:53 -07:00
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
2021-09-24 00:30:04 -07:00
|
|
|
Expanded(child: Padding(padding: EdgeInsets.only(right: 20),
|
|
|
|
child: LineChartTimeSeries(accountManager.accountBalances)))
|
2021-07-12 04:32:49 -07:00
|
|
|
]))),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
2021-07-18 08:59:02 -07:00
|
|
|
|
2021-08-09 07:13:42 -07:00
|
|
|
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 Column(children: [
|
|
|
|
FormBuilderRadioGroup(
|
|
|
|
orientation: OptionsOrientation.horizontal,
|
2021-08-15 09:18:09 -07:00
|
|
|
name: S.of(context).pnl,
|
2021-08-09 07:13:42 -07:00
|
|
|
initialValue: accountManager.pnlSeriesIndex,
|
2021-09-10 02:56:15 -07:00
|
|
|
onChanged: (int? v) {
|
2021-08-09 07:13:42 -07:00
|
|
|
setState(() {
|
2021-09-10 02:56:15 -07:00
|
|
|
accountManager.setPnlSeriesIndex(v!);
|
2021-08-09 07:13:42 -07:00
|
|
|
});
|
|
|
|
},
|
|
|
|
options: [
|
2021-09-14 07:29:40 -07:00
|
|
|
FormBuilderFieldOption(child: Text(S.of(context).price), value: 0),
|
2021-08-23 19:04:45 -07:00
|
|
|
FormBuilderFieldOption(
|
2021-09-14 07:29:40 -07:00
|
|
|
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),
|
2021-08-15 09:18:09 -07:00
|
|
|
FormBuilderFieldOption(child: Text(S.of(context).qty), value: 4),
|
|
|
|
FormBuilderFieldOption(child: Text(S.of(context).table), value: 5),
|
2021-08-09 07:13:42 -07:00
|
|
|
]),
|
|
|
|
Observer(builder: (context) {
|
2021-08-23 19:04:45 -07:00
|
|
|
final _ = accountManager.pnlSorted;
|
2021-08-09 07:13:42 -07:00
|
|
|
return Expanded(
|
2021-09-23 01:37:43 -07:00
|
|
|
child: Padding(
|
|
|
|
padding: EdgeInsets.only(right: 20),
|
|
|
|
child: accountManager.pnlSeriesIndex != 5
|
|
|
|
? PnLChart(
|
|
|
|
accountManager.pnls, accountManager.pnlSeriesIndex)
|
|
|
|
: PnLTable()));
|
2021-08-09 07:13:42 -07:00
|
|
|
})
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PnLChart extends StatelessWidget {
|
2021-08-16 06:07:16 -07:00
|
|
|
final List<PnL> pnls;
|
|
|
|
final int seriesIndex;
|
2021-08-09 07:13:42 -07:00
|
|
|
|
2021-08-16 06:07:16 -07:00
|
|
|
PnLChart(this.pnls, this.seriesIndex);
|
2021-08-09 07:13:42 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-08-16 06:07:16 -07:00
|
|
|
final series = _createSeries(pnls, seriesIndex, context);
|
|
|
|
return LineChartTimeSeries(series);
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
|
2021-08-16 06:07:16 -07:00
|
|
|
static double _seriesData(PnL pnl, int index) {
|
2021-08-09 07:13:42 -07:00
|
|
|
switch (index) {
|
|
|
|
case 0:
|
2021-09-14 07:29:40 -07:00
|
|
|
return pnl.price;
|
2021-08-09 07:13:42 -07:00
|
|
|
case 1:
|
2021-09-14 07:29:40 -07:00
|
|
|
return pnl.realized;
|
2021-08-09 07:13:42 -07:00
|
|
|
case 2:
|
2021-09-14 07:29:40 -07:00
|
|
|
return pnl.unrealized;
|
2021-08-09 07:13:42 -07:00
|
|
|
case 3:
|
2021-09-14 07:29:40 -07:00
|
|
|
return pnl.realized + pnl.unrealized;
|
2021-08-09 07:13:42 -07:00
|
|
|
case 4:
|
|
|
|
return pnl.amount;
|
|
|
|
}
|
2021-09-10 02:56:15 -07:00
|
|
|
return 0.0;
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
|
2021-08-16 06:07:16 -07:00
|
|
|
static List<TimeSeriesPoint<double>> _createSeries(
|
|
|
|
List<PnL> data, int index, BuildContext context) {
|
2021-08-23 19:04:45 -07:00
|
|
|
return data
|
|
|
|
.map((pnl) => TimeSeriesPoint(
|
|
|
|
pnl.timestamp.millisecondsSinceEpoch ~/ DAY_MS,
|
|
|
|
_seriesData(pnl, index)))
|
|
|
|
.toList();
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PnLTable extends StatelessWidget {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-08-23 19:04:45 -07:00
|
|
|
final sortSymbol = accountManager.pnlDesc ? ' \u2193' : ' \u2191';
|
2021-08-09 07:13:42 -07:00
|
|
|
return SingleChildScrollView(
|
2021-09-05 02:48:51 -07:00
|
|
|
child: Observer(
|
|
|
|
builder: (context) => PaginatedDataTable(
|
|
|
|
columns: [
|
|
|
|
DataColumn(
|
2021-09-11 19:27:31 -07:00
|
|
|
label: Text(S.of(context).date + sortSymbol),
|
2021-09-05 02:48:51 -07:00
|
|
|
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],
|
2021-09-10 02:56:15 -07:00
|
|
|
onRowsPerPageChanged: (int? value) {
|
|
|
|
settings.setRowsPerPage(value ?? 25);
|
2021-09-05 02:48:51 -07:00
|
|
|
},
|
|
|
|
showFirstLastButtons: true,
|
|
|
|
rowsPerPage: settings.rowsPerPage,
|
|
|
|
source: PnLDataSource(context))));
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PnLDataSource extends DataTableSource {
|
|
|
|
BuildContext context;
|
|
|
|
final dateFormat = DateFormat("MM-dd");
|
|
|
|
|
|
|
|
PnLDataSource(this.context);
|
|
|
|
|
|
|
|
@override
|
|
|
|
DataRow getRow(int index) {
|
2021-08-23 19:04:45 -07:00
|
|
|
final pnl = accountManager.pnlSorted[index];
|
2021-08-09 07:13:42 -07:00
|
|
|
final ts = dateFormat.format(pnl.timestamp);
|
|
|
|
return DataRow(cells: [
|
|
|
|
DataCell(Text("$ts")),
|
2021-09-21 05:52:52 -07:00
|
|
|
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))),
|
2021-08-09 07:13:42 -07:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool get isRowCountApproximate => false;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get rowCount => accountManager.pnls.length;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get selectedRowCount => 0;
|
|
|
|
}
|