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-06-26 07:30:12 -07:00
|
|
|
import 'package:local_auth/local_auth.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-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-06-26 07:30:12 -07:00
|
|
|
import 'main.dart';
|
2021-08-15 09:18:09 -07:00
|
|
|
import 'generated/l10n.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-06-26 07:30:12 -07:00
|
|
|
if (accountManager.active == null) return CircularProgressIndicator();
|
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-08-09 07:13:42 -07:00
|
|
|
onPressed: _onShieldTAddr,
|
|
|
|
)
|
|
|
|
]);
|
|
|
|
}),
|
|
|
|
Observer(builder: (context) {
|
2021-08-13 20:44:53 -07:00
|
|
|
final balance = accountManager.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;
|
|
|
|
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)!
|
|
|
|
.copyWith(color: theme.colorScheme.primaryVariant);
|
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(
|
|
|
|
"${balanceFX.toStringAsFixed(2)} ${settings.currency}",
|
|
|
|
style: theme.textTheme.headline6),
|
|
|
|
if (fx != 0.0)
|
|
|
|
Text(
|
|
|
|
"1 ${coin.ticker} = ${fx.toStringAsFixed(2)} ${settings.currency}"),
|
|
|
|
]);
|
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-07-09 06:33:39 -07:00
|
|
|
_onShieldTAddr() {
|
|
|
|
showDialog(
|
|
|
|
context: this.context,
|
|
|
|
barrierDismissible: false,
|
|
|
|
builder: (context) => AlertDialog(
|
2021-08-15 09:18:09 -07:00
|
|
|
title: Text(S.of(context).shieldTransparentBalance),
|
2021-09-05 02:48:51 -07:00
|
|
|
content: Text(S
|
|
|
|
.of(context)
|
|
|
|
.doYouWantToTransferYourEntireTransparentBalanceTo(coin.ticker)),
|
2021-08-13 20:44:53 -07:00
|
|
|
actions: confirmButtons(context, () async {
|
2021-08-23 01:25:37 -07:00
|
|
|
final s = S.of(context);
|
2021-08-13 20:44:53 -07:00
|
|
|
Navigator.of(this.context).pop();
|
2021-08-23 19:04:45 -07:00
|
|
|
final snackBar1 = SnackBar(content: Text(s.shieldingInProgress));
|
2021-09-10 02:56:15 -07:00
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar1);
|
2021-08-13 20:44:53 -07:00
|
|
|
final txid = await WarpApi.shieldTAddr(accountManager.active.id);
|
2021-08-23 01:25:37 -07:00
|
|
|
final snackBar2 = SnackBar(content: Text("${s.txId}: $txid"));
|
2021-09-10 02:56:15 -07:00
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
|
2021-08-13 20:44:53 -07:00
|
|
|
})),
|
2021-07-09 06:33:39 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-08-27 19:10:33 -07:00
|
|
|
_onReceive() {
|
|
|
|
showQR(context, _address());
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
return ((b.abs() ~/ 100000) / 1000.0).toStringAsFixed(3);
|
|
|
|
}
|
|
|
|
|
|
|
|
_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-06-26 07:30:12 -07:00
|
|
|
await accountManager.updateUnconfirmedBalance();
|
2021-09-08 07:14:19 -07:00
|
|
|
await contacts.fetchContacts();
|
2021-07-09 06:33:39 -07:00
|
|
|
accountManager.updateTBalance();
|
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 {
|
|
|
|
final localAuth = LocalAuthentication();
|
2021-07-30 05:36:46 -07:00
|
|
|
try {
|
|
|
|
final didAuthenticate = await localAuth.authenticate(
|
2021-08-15 09:18:09 -07:00
|
|
|
localizedReason: S.of(context).pleaseAuthenticateToShowAccountSeed);
|
2021-07-30 05:36:46 -07:00
|
|
|
if (didAuthenticate) {
|
2021-09-11 18:16:53 -07:00
|
|
|
Navigator.of(context).pushNamed('/backup');
|
2021-07-30 05:36:46 -07:00
|
|
|
}
|
2021-08-09 07:13:42 -07:00
|
|
|
} on PlatformException catch (e) {
|
2021-07-30 05:36:46 -07:00
|
|
|
await showDialog(
|
2021-08-09 07:13:42 -07:00
|
|
|
context: context,
|
2021-07-30 05:36:46 -07:00
|
|
|
barrierDismissible: true,
|
|
|
|
builder: (context) => AlertDialog(
|
2021-08-15 09:18:09 -07:00
|
|
|
title: Text(S.of(context).noAuthenticationMethod),
|
2021-09-10 02:56:15 -07:00
|
|
|
content: Text(e.message ?? "")));
|
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
|
|
|
}
|
|
|
|
|
|
|
|
class NoteWidget extends StatefulWidget {
|
2021-09-08 19:56:24 -07:00
|
|
|
final void Function(int index) tabTo;
|
2021-07-12 04:32:49 -07:00
|
|
|
|
|
|
|
NoteWidget(this.tabTo);
|
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => _NoteState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _NoteState extends State<NoteWidget> with AutomaticKeepAliveClientMixin {
|
|
|
|
@override
|
|
|
|
bool get wantKeepAlive => true; //Set to true
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return SingleChildScrollView(
|
2021-09-08 19:56:24 -07:00
|
|
|
padding: EdgeInsets.all(8),
|
2021-06-26 07:30:12 -07:00
|
|
|
scrollDirection: Axis.vertical,
|
2021-08-24 18:30:16 -07:00
|
|
|
child: Observer(builder: (context) {
|
|
|
|
var amountHeader = S.of(context).amount;
|
|
|
|
switch (accountManager.noteSortOrder) {
|
|
|
|
case SortOrder.Ascending:
|
|
|
|
amountHeader += ' \u2191';
|
|
|
|
break;
|
|
|
|
case SortOrder.Descending:
|
|
|
|
amountHeader += ' \u2193';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
}
|
2021-09-15 07:42:44 -07:00
|
|
|
return PaginatedDataTable(
|
|
|
|
columns: [
|
|
|
|
DataColumn(
|
|
|
|
label: settings.showConfirmations
|
|
|
|
? Text(S.of(context).confs)
|
|
|
|
: Text(S.of(context).height),
|
|
|
|
onSort: (_, __) {
|
|
|
|
setState(() {
|
|
|
|
settings.toggleShowConfirmations();
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
DataColumn(label: Text(S.of(context).datetime)),
|
|
|
|
DataColumn(
|
|
|
|
label: Text(amountHeader),
|
|
|
|
numeric: true,
|
|
|
|
onSort: (_, __) {
|
|
|
|
setState(() {
|
|
|
|
accountManager.sortNoteAmount();
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
header: Text(S.of(context).selectNotesToExcludeFromPayments,
|
|
|
|
style: Theme.of(context).textTheme.bodyText2),
|
|
|
|
columnSpacing: 16,
|
|
|
|
showCheckboxColumn: false,
|
|
|
|
availableRowsPerPage: [5, 10, 25, 100],
|
|
|
|
onRowsPerPageChanged: (int? value) {
|
|
|
|
settings.setRowsPerPage(value ?? 25);
|
|
|
|
},
|
|
|
|
showFirstLastButtons: true,
|
|
|
|
rowsPerPage: settings.rowsPerPage,
|
|
|
|
source: NotesDataSource(context, _onRowSelected),
|
|
|
|
);
|
2021-08-24 18:30:16 -07:00
|
|
|
}));
|
2021-07-12 04:32:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
_onRowSelected(Note note) {
|
|
|
|
accountManager.excludeNote(note);
|
2021-07-10 22:20:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class NotesDataSource extends DataTableSource {
|
2021-07-12 04:32:49 -07:00
|
|
|
final BuildContext context;
|
|
|
|
final Function(Note) onRowSelected;
|
2021-07-10 22:20:53 -07:00
|
|
|
|
2021-07-12 04:32:49 -07:00
|
|
|
NotesDataSource(this.context, this.onRowSelected);
|
2021-07-10 22:20:53 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
DataRow getRow(int index) {
|
2021-08-16 06:07:16 -07:00
|
|
|
final note = accountManager.sortedNotes[index];
|
2021-07-18 08:59:02 -07:00
|
|
|
final theme = Theme.of(context);
|
2021-08-09 07:13:42 -07:00
|
|
|
final confsOrHeight = settings.showConfirmations
|
|
|
|
? syncStatus.latestHeight - note.height + 1
|
|
|
|
: note.height;
|
2021-08-14 01:02:30 -07:00
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
var style = theme.textTheme.bodyText2!;
|
2021-09-09 03:09:14 -07:00
|
|
|
if (!_confirmed(note.height))
|
2021-09-10 02:56:15 -07:00
|
|
|
style = style.copyWith(color: style.color!.withOpacity(0.5));
|
2021-09-09 03:09:14 -07:00
|
|
|
|
2021-08-14 01:02:30 -07:00
|
|
|
if (note.spent)
|
|
|
|
style = style.merge(TextStyle(decoration: TextDecoration.lineThrough));
|
|
|
|
|
2021-09-09 08:23:10 -07:00
|
|
|
final amountStyle = fontWeight(style, note.value);
|
2021-09-09 03:09:14 -07:00
|
|
|
|
2021-07-12 04:32:49 -07:00
|
|
|
return DataRow.byIndex(
|
|
|
|
index: index,
|
|
|
|
selected: note.excluded,
|
2021-07-18 08:59:02 -07:00
|
|
|
color: MaterialStateColor.resolveWith((states) =>
|
|
|
|
states.contains(MaterialState.selected)
|
|
|
|
? theme.primaryColor.withOpacity(0.5)
|
|
|
|
: theme.backgroundColor),
|
2021-07-10 22:20:53 -07:00
|
|
|
cells: [
|
2021-08-23 19:04:45 -07:00
|
|
|
DataCell(Text("$confsOrHeight", style: style)),
|
2021-08-14 01:02:30 -07:00
|
|
|
DataCell(Text("${note.timestamp}", style: style)),
|
2021-09-09 08:23:10 -07:00
|
|
|
DataCell(Text("${note.value.toStringAsFixed(8)}", style: amountStyle)),
|
2021-07-10 22:20:53 -07:00
|
|
|
],
|
2021-07-12 04:32:49 -07:00
|
|
|
onSelectChanged: (selected) => _noteSelected(note, selected),
|
2021-07-10 22:20:53 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool get isRowCountApproximate => false;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get rowCount => accountManager.notes.length;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get selectedRowCount => 0;
|
|
|
|
|
|
|
|
bool _confirmed(int height) {
|
|
|
|
return syncStatus.latestHeight - height >= settings.anchorOffset;
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
2021-07-12 04:32:49 -07:00
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
void _noteSelected(Note note, bool? selected) {
|
2021-07-12 04:32:49 -07:00
|
|
|
note.excluded = !note.excluded;
|
|
|
|
notifyListeners();
|
|
|
|
onRowSelected(note);
|
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
class HistoryWidget extends StatefulWidget {
|
2021-09-09 03:09:14 -07:00
|
|
|
final void Function(int index) tabTo;
|
2021-07-12 04:32:49 -07:00
|
|
|
|
|
|
|
HistoryWidget(this.tabTo);
|
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
@override
|
2021-08-09 07:13:42 -07:00
|
|
|
State<StatefulWidget> createState() => HistoryState();
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-08-09 07:13:42 -07:00
|
|
|
class HistoryState extends State<HistoryWidget>
|
2021-06-26 07:30:12 -07:00
|
|
|
with AutomaticKeepAliveClientMixin {
|
|
|
|
@override
|
|
|
|
bool get wantKeepAlive => true; //Set to true
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
super.build(context);
|
|
|
|
return SingleChildScrollView(
|
2021-07-12 04:32:49 -07:00
|
|
|
padding: EdgeInsets.all(4),
|
2021-06-26 07:30:12 -07:00
|
|
|
scrollDirection: Axis.vertical,
|
2021-08-24 18:30:16 -07:00
|
|
|
child: Observer(builder: (context) {
|
|
|
|
var amountHeader = S.of(context).amount;
|
|
|
|
switch (accountManager.txSortOrder) {
|
|
|
|
case SortOrder.Ascending:
|
|
|
|
amountHeader += ' \u2191';
|
|
|
|
break;
|
|
|
|
case SortOrder.Descending:
|
|
|
|
amountHeader += ' \u2193';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
}
|
2021-09-15 07:42:44 -07:00
|
|
|
return PaginatedDataTable(
|
|
|
|
columns: [
|
|
|
|
DataColumn(
|
|
|
|
label: settings.showConfirmations
|
|
|
|
? Text(S.of(context).confs)
|
|
|
|
: Text(S.of(context).height),
|
|
|
|
onSort: (_, __) {
|
|
|
|
setState(() {
|
|
|
|
settings.toggleShowConfirmations();
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
DataColumn(label: Text(S.of(context).datetime)),
|
|
|
|
DataColumn(
|
|
|
|
label: Text(amountHeader),
|
|
|
|
numeric: true,
|
|
|
|
onSort: (_, __) {
|
|
|
|
setState(() {
|
|
|
|
accountManager.sortTxAmount();
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
DataColumn(label: Text(S.of(context).txId)),
|
|
|
|
DataColumn(label: Text(S.of(context).address)),
|
|
|
|
DataColumn(label: Text(S.of(context).memo)),
|
|
|
|
],
|
|
|
|
columnSpacing: 16,
|
|
|
|
showCheckboxColumn: false,
|
|
|
|
availableRowsPerPage: [5, 10, 25, 100],
|
|
|
|
onRowsPerPageChanged: (int? value) {
|
|
|
|
settings.setRowsPerPage(value ?? 25);
|
2021-08-24 18:30:16 -07:00
|
|
|
},
|
2021-09-15 07:42:44 -07:00
|
|
|
showFirstLastButtons: true,
|
|
|
|
rowsPerPage: settings.rowsPerPage,
|
|
|
|
source: HistoryDataSource(context));
|
2021-08-24 18:30:16 -07:00
|
|
|
}));
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-10 22:20:53 -07:00
|
|
|
class HistoryDataSource extends DataTableSource {
|
|
|
|
BuildContext context;
|
|
|
|
|
|
|
|
HistoryDataSource(this.context);
|
|
|
|
|
|
|
|
@override
|
|
|
|
DataRow getRow(int index) {
|
2021-08-16 06:07:16 -07:00
|
|
|
final tx = accountManager.sortedTxs[index];
|
2021-08-09 07:13:42 -07:00
|
|
|
final confsOrHeight = settings.showConfirmations
|
|
|
|
? syncStatus.latestHeight - tx.height + 1
|
|
|
|
: tx.height;
|
2021-09-09 03:09:14 -07:00
|
|
|
final color = amountColor(context, tx.value);
|
2021-09-10 02:56:15 -07:00
|
|
|
var style = Theme.of(context).textTheme.bodyText2!.copyWith(color: color);
|
2021-09-09 03:09:14 -07:00
|
|
|
style = fontWeight(style, tx.value);
|
2021-09-15 07:42:44 -07:00
|
|
|
final a = tx.contact ?? addressLeftTrim(tx.address);
|
|
|
|
final m = tx.memo.substring(0, min(tx.memo.length, 32));
|
2021-09-09 03:09:14 -07:00
|
|
|
|
2021-07-10 22:20:53 -07:00
|
|
|
return DataRow(
|
|
|
|
cells: [
|
2021-07-30 02:45:43 -07:00
|
|
|
DataCell(Text("$confsOrHeight")),
|
2021-07-10 22:20:53 -07:00
|
|
|
DataCell(Text("${tx.timestamp}")),
|
2021-09-15 07:42:44 -07:00
|
|
|
DataCell(Text("${tx.value.toStringAsFixed(8)}",
|
|
|
|
style: style, textAlign: TextAlign.left)),
|
2021-09-14 06:47:51 -07:00
|
|
|
DataCell(Text("${tx.txid}")),
|
2021-09-15 07:42:44 -07:00
|
|
|
DataCell(Text("$a")),
|
|
|
|
DataCell(Text("$m")),
|
2021-07-10 22:20:53 -07:00
|
|
|
],
|
|
|
|
onSelectChanged: (_) {
|
|
|
|
Navigator.of(this.context).pushNamed('/tx', arguments: tx);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool get isRowCountApproximate => false;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get rowCount => accountManager.txs.length;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get selectedRowCount => 0;
|
|
|
|
}
|
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-07-12 04:32:49 -07:00
|
|
|
Expanded(
|
2021-08-16 06:07:16 -07:00
|
|
|
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(
|
|
|
|
child: accountManager.pnlSeriesIndex != 5
|
2021-08-16 06:07:16 -07:00
|
|
|
? PnLChart(accountManager.pnls, accountManager.pnlSeriesIndex)
|
2021-08-09 07:13:42 -07:00
|
|
|
: PnLTable());
|
|
|
|
})
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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")),
|
|
|
|
DataCell(Text("${pnl.amount.toStringAsFixed(2)}")),
|
|
|
|
DataCell(Text("${pnl.price.toStringAsFixed(3)}")),
|
|
|
|
DataCell(Text("${pnl.realized.toStringAsFixed(3)}")),
|
|
|
|
DataCell(Text("${pnl.unrealized.toStringAsFixed(3)}")),
|
|
|
|
DataCell(Text("${(pnl.realized + pnl.unrealized).toStringAsFixed(3)}")),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool get isRowCountApproximate => false;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get rowCount => accountManager.pnls.length;
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get selectedRowCount => 0;
|
|
|
|
}
|