2021-06-26 07:30:12 -07:00
|
|
|
import 'dart:async';
|
2022-07-16 20:29:51 -07:00
|
|
|
import 'dart:math';
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
2021-07-10 22:20:53 -07:00
|
|
|
import 'package:flutter/services.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
2022-08-16 07:48:49 -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';
|
2022-06-16 03:16:32 -07:00
|
|
|
import 'package:animated_text_kit/animated_text_kit.dart';
|
2022-07-16 20:29:51 -07:00
|
|
|
import 'package:velocity_x/velocity_x.dart';
|
|
|
|
import 'package:warp_api/warp_api.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
|
2021-08-15 09:18:09 -07:00
|
|
|
import 'generated/l10n.dart';
|
2022-03-07 06:53:18 -08:00
|
|
|
import 'main.dart';
|
|
|
|
import 'store.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
|
2022-04-18 23:49:46 -07:00
|
|
|
class AccountPage extends StatefulWidget {
|
2021-06-26 07:30:12 -07:00
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
_AccountState createState() => _AccountState();
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2022-04-18 23:49:46 -07:00
|
|
|
class _AccountState extends State<AccountPage> with AutomaticKeepAliveClientMixin {
|
2021-06-26 07:30:12 -07:00
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
bool get wantKeepAlive => true; //Set to true
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
Widget build(BuildContext context) {
|
|
|
|
super.build(context);
|
|
|
|
return SingleChildScrollView(
|
2022-03-30 22:25:52 -07:00
|
|
|
child: Column(children: [
|
2022-03-07 06:53:18 -08:00
|
|
|
SyncStatusWidget(),
|
|
|
|
QRAddressWidget(),
|
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
|
|
|
BalanceWidget(),
|
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
|
|
|
MemPoolWidget(),
|
|
|
|
ProgressWidget(),
|
2022-03-30 22:25:52 -07:00
|
|
|
])
|
|
|
|
);
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
2022-03-07 06:53:18 -08:00
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
|
2022-07-16 20:29:51 -07:00
|
|
|
class SyncStatusWidget extends StatefulWidget {
|
|
|
|
SyncStatusState createState() => SyncStatusState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class SyncStatusState extends State<SyncStatusWidget> {
|
|
|
|
var display = 0;
|
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2022-03-07 06:53:18 -08:00
|
|
|
final s = S.of(context);
|
|
|
|
final theme = Theme.of(context);
|
2022-02-22 21:20:45 -08:00
|
|
|
final simpleMode = settings.simpleMode;
|
2022-07-16 20:29:51 -07:00
|
|
|
final syncStyle = theme.textTheme.caption!.apply(color: theme.primaryColor);
|
|
|
|
|
|
|
|
dynamic createText(String text, bool animated) {
|
|
|
|
return animated ? WavyAnimatedText(text, textStyle: syncStyle) : Text(text, style: syncStyle);
|
|
|
|
}
|
2021-09-05 21:41:32 -07:00
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
return Column(children: [
|
2022-03-16 09:12:05 -07:00
|
|
|
if (simpleMode) Padding(padding: EdgeInsets.fromLTRB(0, 8, 0, 0), child: Text(s.simpleMode)),
|
2022-03-07 06:53:18 -08:00
|
|
|
Observer(builder: (context) {
|
|
|
|
final time = eta.eta;
|
2022-09-02 01:47:48 -07:00
|
|
|
final neverSynced = syncStatus.syncedHeight == null;
|
2022-08-29 20:42:08 -07:00
|
|
|
final syncedHeight = syncStatus.syncedHeight ?? 0;
|
|
|
|
final startHeight = syncStatus.startSyncedHeight;
|
2022-07-16 20:29:51 -07:00
|
|
|
final timestamp = syncStatus.timestamp?.timeAgo() ?? s.na;
|
2022-03-07 06:53:18 -08:00
|
|
|
final latestHeight = syncStatus.latestHeight;
|
2022-07-16 20:29:51 -07:00
|
|
|
final remaining = syncedHeight != null ? max(latestHeight-syncedHeight, 0) : 0;
|
2022-08-29 20:42:08 -07:00
|
|
|
final total = latestHeight - startHeight;
|
|
|
|
final percent = total > 0 ? 100 * (syncedHeight - startHeight) ~/ total : 0;
|
2022-08-16 07:48:49 -07:00
|
|
|
final downloadedSize = NumberFormat.compact().format(syncStatus.downloadedSize);
|
|
|
|
final trialDecryptionCount = NumberFormat.compact().format(syncStatus.trialDecryptionCount);
|
2022-07-21 19:35:21 -07:00
|
|
|
final disconnected = latestHeight == 0;
|
|
|
|
final synced = syncStatus.isSynced();
|
|
|
|
final paused = syncStatus.paused;
|
|
|
|
final syncing = !paused && !disconnected && !neverSynced && !synced;
|
2022-07-16 20:29:51 -07:00
|
|
|
|
|
|
|
dynamic createSyncText(int iDisplay, bool animated) {
|
|
|
|
switch (iDisplay) {
|
|
|
|
case 0:
|
|
|
|
return createText('$syncedHeight / $latestHeight', animated);
|
|
|
|
case 1:
|
2022-08-29 20:42:08 -07:00
|
|
|
final m = syncStatus.isRescan ? "RESCAN" : "CATCH UP";
|
|
|
|
return createText('$m $percent %', animated);
|
2022-07-16 20:29:51 -07:00
|
|
|
case 2:
|
|
|
|
return createText('$remaining...', animated);
|
|
|
|
case 3:
|
|
|
|
return createText('$timestamp', animated);
|
|
|
|
case 4:
|
|
|
|
return createText('$time', animated);
|
2022-08-16 07:48:49 -07:00
|
|
|
case 5:
|
|
|
|
return createText('\u{2193} $downloadedSize', animated);
|
|
|
|
case 6:
|
|
|
|
return createText('\u{2192} $trialDecryptionCount', animated);
|
2022-07-16 20:29:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dynamic createSyncStatus() {
|
2022-08-16 07:48:49 -07:00
|
|
|
var d = display % 8;
|
2022-07-16 20:29:51 -07:00
|
|
|
if (d == 0)
|
|
|
|
return AnimatedTextKit(
|
|
|
|
key: ValueKey(syncedHeight),
|
|
|
|
repeatForever: true,
|
2022-08-16 19:58:15 -07:00
|
|
|
animatedTexts: [ for (int i = 0; i < 7; i++) createSyncText(i, true) ],
|
2022-07-16 20:29:51 -07:00
|
|
|
onTap: () => setState(() { display += 1; }),
|
|
|
|
);
|
|
|
|
return createSyncText(d-1, false);
|
|
|
|
}
|
|
|
|
|
2022-07-21 19:35:21 -07:00
|
|
|
final text;
|
|
|
|
if (paused)
|
|
|
|
text = Text(s.syncPaused);
|
|
|
|
else if (disconnected)
|
|
|
|
text = Text(s.disconnected);
|
|
|
|
else if (neverSynced)
|
|
|
|
text = Text(s.rescanNeeded);
|
|
|
|
else if (synced)
|
|
|
|
text = Text('$syncedHeight', style: theme.textTheme.caption);
|
|
|
|
else
|
|
|
|
text = createSyncStatus();
|
|
|
|
|
|
|
|
return TextButton(onPressed: () { syncing ? _onNextDisplay() : _onSync(context); }, child: text);
|
2022-03-07 06:53:18 -08:00
|
|
|
})
|
|
|
|
]);
|
2021-07-12 04:32:49 -07:00
|
|
|
}
|
2022-03-16 00:43:38 -07:00
|
|
|
|
2022-07-08 18:51:23 -07:00
|
|
|
_onSync(BuildContext context) {
|
2022-07-21 19:35:21 -07:00
|
|
|
if (syncStatus.paused)
|
|
|
|
syncStatus.setPause(false);
|
|
|
|
else if (syncStatus.syncedHeight != null)
|
2022-08-29 20:42:08 -07:00
|
|
|
Future.microtask(() => syncStatus.sync(false));
|
2022-07-08 18:51:23 -07:00
|
|
|
else
|
|
|
|
rescan(context);
|
2022-03-16 00:43:38 -07:00
|
|
|
}
|
2022-07-21 19:35:21 -07:00
|
|
|
|
|
|
|
_onNextDisplay() {
|
|
|
|
setState(() { display += 1; });
|
|
|
|
}
|
2022-03-07 06:53:18 -08:00
|
|
|
}
|
2021-07-12 04:32:49 -07:00
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
class QRAddressWidget extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
QRAddressState createState() => QRAddressState();
|
|
|
|
}
|
2021-08-27 02:51:34 -07:00
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
class QRAddressState extends State<QRAddressWidget> {
|
|
|
|
bool _useSnapAddress = false;
|
|
|
|
String _snapAddress = "";
|
2021-07-10 22:20:53 -07:00
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Observer(builder: (context) {
|
|
|
|
final s = S.of(context);
|
|
|
|
final theme = Theme.of(context);
|
2022-07-16 20:29:51 -07:00
|
|
|
final _ = active.taddress;
|
2022-03-07 06:53:18 -08:00
|
|
|
final simpleMode = settings.simpleMode;
|
|
|
|
final address = _address();
|
2022-04-15 23:51:13 -07:00
|
|
|
final shortAddress = centerTrim(address);
|
2022-03-07 06:53:18 -08:00
|
|
|
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;
|
|
|
|
|
|
|
|
return Column(children: [
|
|
|
|
if (hasTAddr)
|
|
|
|
Text(showTAddr
|
|
|
|
? s.tapQrCodeForShieldedAddress
|
|
|
|
: s.tapQrCodeForTransparentAddress),
|
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
|
|
|
|
GestureDetector(
|
|
|
|
onTap: hasTAddr ? _onQRTap : null,
|
2022-07-16 20:29:51 -07:00
|
|
|
onLongPress: _onUpdateTAddr,
|
2022-03-07 06:53:18 -08:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
)
|
|
|
|
]);
|
|
|
|
});
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-07-09 06:33:39 -07:00
|
|
|
_onQRTap() {
|
2022-03-07 06:53:18 -08:00
|
|
|
active.toggleShowTAddr();
|
2021-07-09 06:33:39 -07:00
|
|
|
}
|
|
|
|
|
2021-07-10 22:20:53 -07:00
|
|
|
_onAddressCopy() {
|
|
|
|
Clipboard.setData(ClipboardData(text: _address()));
|
2022-07-16 20:29:51 -07:00
|
|
|
showSnackBar(S.of(context).addressCopiedToClipboard);
|
2021-07-10 22:20:53 -07:00
|
|
|
}
|
|
|
|
|
2021-09-25 02:09:41 -07:00
|
|
|
_onReceive() async {
|
2021-10-08 23:12:51 -07:00
|
|
|
Navigator.of(context).pushNamed('/receive', arguments: _address());
|
2021-08-27 19:10:33 -07:00
|
|
|
}
|
|
|
|
|
2021-07-07 21:22:54 -07:00
|
|
|
_onSnapAddress() {
|
2022-03-07 06:53:18 -08:00
|
|
|
final address = active.newAddress();
|
2021-07-07 21:22:54 -07:00
|
|
|
setState(() {
|
|
|
|
_useSnapAddress = true;
|
|
|
|
_snapAddress = address;
|
|
|
|
});
|
|
|
|
Timer(Duration(seconds: 15), () {
|
|
|
|
setState(() {
|
|
|
|
_useSnapAddress = false;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-07-09 06:33:39 -07:00
|
|
|
|
2022-07-16 20:29:51 -07:00
|
|
|
_onUpdateTAddr() async {
|
|
|
|
if (!active.showTAddr) return;
|
2022-07-23 06:30:46 -07:00
|
|
|
final s = S.of(context);
|
2022-07-18 05:33:30 -07:00
|
|
|
final coinIndex = active.coinDef.coinIndex;
|
2022-07-16 20:29:51 -07:00
|
|
|
var pathController = TextEditingController();
|
2022-07-23 06:30:46 -07:00
|
|
|
var keyController = TextEditingController();
|
2022-07-16 20:29:51 -07:00
|
|
|
final confirmed = await showDialog<bool>(context: context, builder: (context) => AlertDialog(
|
|
|
|
title: Text(S.of(context).changeTransparentKey),
|
2022-07-23 06:30:46 -07:00
|
|
|
content: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
TextField(
|
|
|
|
decoration: InputDecoration(label: Text(s.derivationPath), hintText: "m/44'/${coinIndex}'/0'/0/0"),
|
|
|
|
controller: pathController),
|
|
|
|
TextField(
|
|
|
|
decoration: InputDecoration(label: Text(s.privateKey)),
|
|
|
|
controller: keyController)]),
|
2022-07-16 20:29:51 -07:00
|
|
|
actions: confirmButtons(context, () {
|
|
|
|
Navigator.of(context).pop(true);
|
|
|
|
}))) ?? false;
|
|
|
|
if (confirmed) {
|
2022-07-23 06:30:46 -07:00
|
|
|
if (pathController.text.isNotEmpty)
|
|
|
|
WarpApi.importTransparentPath(active.coin, active.id, pathController.text);
|
|
|
|
if (keyController.text.isNotEmpty)
|
|
|
|
WarpApi.importTransparentSecretKey(active.coin, active.id, keyController.text);
|
2022-07-16 20:29:51 -07:00
|
|
|
if (WarpApi.getError()) {
|
|
|
|
showSnackBar(WarpApi.getErrorMessage());
|
|
|
|
}
|
2022-07-23 19:46:46 -07:00
|
|
|
else {
|
|
|
|
await active.refreshTAddr();
|
|
|
|
active.updateTBalance();
|
|
|
|
}
|
2022-07-16 20:29:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
String _address() {
|
|
|
|
final address = active.showTAddr
|
|
|
|
? active.taddress
|
|
|
|
: _useSnapAddress
|
|
|
|
? _snapAddress
|
|
|
|
: active.account.address;
|
|
|
|
return address;
|
2021-09-13 19:02:20 -07:00
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
class BalanceWidget extends StatelessWidget {
|
2021-07-12 04:32:49 -07:00
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
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);
|
|
|
|
|
|
|
|
final fx = priceStore.coinPrice;
|
|
|
|
final balanceFX = balance * fx / ZECUNIT;
|
|
|
|
final coinDef = active.coinDef;
|
|
|
|
|
|
|
|
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)}"),
|
|
|
|
]);
|
|
|
|
});
|
2021-07-12 04:32:49 -07:00
|
|
|
}
|
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
class MemPoolWidget extends StatelessWidget {
|
2021-07-12 04:32:49 -07:00
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
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));
|
|
|
|
|
|
|
|
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),
|
|
|
|
]);
|
|
|
|
});
|
2021-07-12 04:32:49 -07:00
|
|
|
}
|
2021-07-18 08:59:02 -07:00
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
class ProgressWidget extends StatefulWidget {
|
2021-08-09 07:13:42 -07:00
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
ProgressState createState() => ProgressState();
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
class ProgressState extends State<ProgressWidget> {
|
|
|
|
int _progress = 0;
|
|
|
|
StreamSubscription? _progressDispose;
|
2021-08-09 07:13:42 -07:00
|
|
|
|
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_progressDispose = progressStream.listen((percent) {
|
|
|
|
setState(() {
|
|
|
|
_progress = percent;
|
|
|
|
});
|
|
|
|
});
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2022-03-07 06:53:18 -08:00
|
|
|
void dispose() {
|
|
|
|
_progressDispose?.cancel();
|
|
|
|
super.dispose();
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2022-06-16 18:30:10 -07:00
|
|
|
final theme = Theme.of(context);
|
|
|
|
|
2022-06-16 03:16:32 -07:00
|
|
|
return Observer(builder: (context) => Column(children: [
|
|
|
|
if (active.banner.isNotEmpty) DefaultTextStyle(
|
2022-06-16 18:30:10 -07:00
|
|
|
style: theme.textTheme.titleLarge!,
|
2022-06-16 03:16:32 -07:00
|
|
|
child: AnimatedTextKit(
|
|
|
|
repeatForever: true,
|
|
|
|
animatedTexts: [
|
|
|
|
TypewriterAnimatedText(active.banner)]
|
|
|
|
)),
|
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
|
|
|
if (_progress != 0) LinearProgressIndicator(value: _progress / 100.0),
|
|
|
|
]));
|
2021-08-09 07:13:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
_getBalanceHi(int b) => decimalFormat((b.abs() ~/ 100000) / 1000.0, 3);
|
2021-08-09 07:13:42 -07:00
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
_getBalanceLo(int b) => (b.abs() % 100000).toString().padLeft(5, '0');
|
2021-08-09 07:13:42 -07:00
|
|
|
|
2022-03-07 06:53:18 -08:00
|
|
|
_sign(int b) => b < 0 ? '-' : '+';
|