zwallet/lib/home.dart

436 lines
12 KiB
Dart
Raw Normal View History

2022-03-07 06:53:18 -08:00
import 'dart:async';
2022-10-04 23:27:53 -07:00
import 'dart:convert';
2022-06-20 02:05:48 -07:00
import 'dart:io';
2022-03-07 06:53:18 -08:00
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
2022-10-04 23:27:53 -07:00
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
2022-03-07 06:53:18 -08:00
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart';
2022-10-04 23:27:53 -07:00
import 'package:warp_api/types.dart';
2022-03-07 06:53:18 -08:00
import 'package:warp_api/warp_api.dart';
2022-04-16 23:03:33 -07:00
import 'package:badges/badges.dart';
2022-03-07 06:53:18 -08:00
import 'about.dart';
import 'account.dart';
2022-06-22 01:43:24 -07:00
import 'animated_qr.dart';
2022-03-07 06:53:18 -08:00
import 'budget.dart';
import 'contact.dart';
import 'history.dart';
import 'generated/l10n.dart';
import 'main.dart';
2022-04-15 23:51:13 -07:00
import 'message.dart';
2022-03-07 06:53:18 -08:00
import 'note.dart';
import 'store.dart';
2022-03-12 05:09:08 -08:00
class HomePage extends StatefulWidget {
2022-03-07 06:53:18 -08:00
@override
HomeState createState() => HomeState();
}
2022-03-12 05:09:08 -08:00
class HomeState extends State<HomePage> {
2022-03-07 06:53:18 -08:00
StreamSubscription? _syncDispose;
2022-10-12 18:25:29 -07:00
Timer? _syncTimer;
Timer? _priceTimer;
2022-03-07 06:53:18 -08:00
@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(false);
2022-03-07 06:53:18 -08:00
await contacts.fetchContacts();
2022-10-12 18:25:29 -07:00
_syncTimer?.cancel();
_syncTimer = Timer.periodic(Duration(seconds: 15), (Timer t) async {
syncStatus.sync(false);
2022-09-02 01:47:48 -07:00
if (active.id != 0) {
await active.updateBalances();
}
2022-03-07 06:53:18 -08:00
});
2022-10-12 18:25:29 -07:00
_priceTimer?.cancel();
_priceTimer = Timer.periodic(Duration(minutes: 5), (Timer t) async {
2022-03-07 06:53:18 -08:00
await priceStore.updateChart();
});
});
2022-10-04 23:27:53 -07:00
_syncDispose = syncStream.listen((p) async {
if (p is String) {
final pjs = jsonDecode(p);
final progress = Progress.fromJson(pjs);
syncStatus.setProgress(progress);
}
final syncedHeight = await syncStatus.getDbSyncedHeight();
if (syncedHeight != null) {
final h = syncedHeight.height;
syncStatus.setSyncHeight(h, syncedHeight.timestamp);
2022-03-07 06:53:18 -08:00
eta.checkpoint(h, DateTime.now());
2022-09-14 19:19:50 -07:00
await active.update();
2022-10-04 23:27:53 -07:00
final progress = syncStatus.progress;
2022-10-06 07:53:44 -07:00
if (progress != null && isMobile()) {
2022-10-04 23:27:53 -07:00
FlutterForegroundTask.updateService(
notificationText: "${progress} %");
}
2022-10-23 03:30:28 -07:00
syncStats.add(DateTime.now(), syncedHeight.height);
2022-03-07 06:53:18 -08:00
}
});
}
@override
2022-03-12 05:09:08 -08:00
void dispose() {
_syncDispose?.cancel();
2022-10-12 18:25:29 -07:00
_syncTimer?.cancel();
_priceTimer?.cancel();
2022-03-12 05:09:08 -08:00
super.dispose();
}
@override
Widget build(BuildContext context) => Observer(builder: (context) {
final _simpleMode = settings.simpleMode;
2022-07-07 20:26:20 -07:00
return HomeInnerPage(key: UniqueKey());
2022-03-12 05:09:08 -08:00
});
}
class HomeInnerPage extends StatefulWidget {
HomeInnerPage({Key? key}) : super(key: key);
@override
HomeInnerState createState() => HomeInnerState();
}
class HomeInnerState extends State<HomeInnerPage> with SingleTickerProviderStateMixin {
TabController? _tabController;
int _tabIndex = 0;
final contactKey = GlobalKey<ContactsState>();
@override
void initState() {
super.initState();
2022-10-12 18:25:29 -07:00
if (Platform.isAndroid)
2022-10-06 07:53:44 -07:00
_initForegroundTask();
2022-04-15 23:51:13 -07:00
final tabController = TabController(length: settings.simpleMode ? 4 : 7, vsync: this);
2022-03-07 06:53:18 -08:00
tabController.addListener(() {
setState(() {
_tabIndex = tabController.index;
});
});
_tabController = tabController;
}
@override
2022-03-12 05:09:08 -08:00
Widget build(BuildContext context) {
final s = S.of(context);
final theme = Theme.of(context);
final simpleMode = settings.simpleMode;
2022-04-18 23:49:46 -07:00
final contactTabIndex = simpleMode ? 3 : 6;
2022-03-12 05:09:08 -08:00
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),
);
2022-04-18 23:49:46 -07:00
else if (_tabIndex == 1)
2022-04-15 23:51:13 -07:00
button = FloatingActionButton(
onPressed: _onSend,
backgroundColor: theme.colorScheme.secondary,
2022-04-18 23:49:46 -07:00
child: Icon(Icons.send),
2022-04-15 23:51:13 -07:00
);
2022-03-12 05:09:08 -08:00
2022-04-16 23:03:33 -07:00
return Observer(builder: (context) {
final _1 = active.dataEpoch;
2022-07-21 19:35:21 -07:00
final _2 = syncStatus.paused;
final _3 = syncStatus.syncing;
final rescanMsg;
if (syncStatus.paused)
rescanMsg = s.resumeScan;
else if (syncStatus.syncing)
rescanMsg = s.cancelScan;
else
rescanMsg = s.rescan;
2022-04-16 23:03:33 -07:00
final unread = active.unread;
final messageTab = unread != 0 ?
Tab(child: Badge(
child: Text(s.messages),
badgeContent: Text('$unread'))) :
Tab(text: s.messages);
2022-07-07 20:26:20 -07:00
if (active.id == 0) {
Future.microtask(() async {
Navigator.of(context).pushReplacementNamed('/accounts');
});
return SizedBox(); // Show a placeholder
}
2022-07-21 19:35:21 -07:00
final menu = PopupMenuButton<String>(
itemBuilder: (context) {
return [
PopupMenuItem(child: Text(s.accounts), value: "Accounts"),
PopupMenuItem(child: Text(s.backup), value: "Backup"),
PopupMenuItem(child: Text(rescanMsg), value: "Rescan"),
2022-12-05 09:45:35 -08:00
if (!simpleMode) PopupMenuItem(child: Text(s.pools), value: "Pools"),
2022-07-21 19:35:21 -07:00
if (!simpleMode)
PopupMenuItem(child:
PopupMenuButton(
child: Text(s.advanced),
itemBuilder: (_) => [
PopupMenuItem(child: Text(s.convertToWatchonly), enabled: active.canPay, value: "Cold"),
PopupMenuItem(child: Text(s.signOffline), enabled: active.canPay, value: "Sign"),
PopupMenuItem(child: Text(s.broadcast), value: "Broadcast"),
PopupMenuItem(child: Text(s.multipay), value: "MultiPay"),
2022-07-26 19:16:36 -07:00
PopupMenuItem(child: Text(s.keyTool), value: "KeyTool"),
2022-07-21 19:35:21 -07:00
], onSelected: _onMenu)),
// if (!simpleMode && !isMobile())
// PopupMenuItem(child: Text(s.ledger), value: "Ledger"),
2022-09-02 01:47:48 -07:00
if (settings.isDeveloper)
PopupMenuItem(child: Text(s.expert), value: "Expert"),
2022-07-21 19:35:21 -07:00
PopupMenuItem(child: Text(s.settings), value: "Settings"),
PopupMenuItem(child: Text(s.help), value: "Help"),
PopupMenuItem(child: Text(s.about), value: "About"),
];
},
onSelected: _onMenu,
);
2022-07-07 20:26:20 -07:00
showAboutOnce(this.context);
2022-11-25 01:26:12 -08:00
return WithForegroundTask(child:
Scaffold(
appBar: AppBar(
centerTitle: true,
title: GestureDetector(
onTap: () => settings.tapDeveloperMode(context),
child: Text("${active.account.name}")),
bottom: TabBar(
2022-04-16 23:03:33 -07:00
controller: _tabController,
2022-11-25 01:26:12 -08:00
isScrollable: true,
tabs: [
Tab(text: s.account),
messageTab,
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),
2022-04-16 23:03:33 -07:00
],
2022-06-24 17:19:44 -07:00
),
2022-11-25 01:26:12 -08:00
actions: [menu],
),
body: TabBarView(
controller: _tabController,
children: [
AccountPage(),
MessageWidget(messageKey),
if (!simpleMode) NoteWidget(),
HistoryWidget(),
if (!simpleMode) BudgetWidget(),
if (!simpleMode) PnLWidget(),
ContactsTab(key: contactKey),
],
),
floatingActionButton: button,
));
2022-04-16 23:03:33 -07:00
});
2022-03-07 06:53:18 -08:00
}
_onSend() {
2022-07-26 19:16:36 -07:00
Navigator.of(context).pushNamed('/send');
2022-03-07 06:53:18 -08:00
}
_onMenu(String choice) {
switch (choice) {
case "Accounts":
2022-07-26 19:16:36 -07:00
Navigator.of(context).pushNamed('/accounts');
2022-03-07 06:53:18 -08:00
break;
case "Backup":
_backup();
break;
case "Rescan":
_rescan();
break;
2022-12-05 08:21:17 -08:00
case "Pools":
Navigator.of(context).pushNamed('/pools');
break;
2022-03-07 06:53:18 -08:00
case "Cold":
_cold();
break;
case "MultiPay":
_multiPay();
break;
2022-04-13 02:05:05 -07:00
case "Sign":
_sign();
break;
2022-03-07 06:53:18 -08:00
case "Broadcast":
_broadcast();
break;
2022-07-26 19:16:36 -07:00
case "KeyTool":
2022-07-27 01:53:45 -07:00
_keyTool();
2022-07-26 19:16:36 -07:00
break;
2022-09-02 01:47:48 -07:00
case "Expert":
Navigator.of(context).pushNamed('/dev');
break;
2022-03-30 22:25:52 -07:00
case "Ledger":
_ledger();
break;
2022-03-07 06:53:18 -08:00
case "Settings":
_settings();
break;
case "Help":
2022-06-21 17:22:46 -07:00
launchUrl(Uri.parse(DOC_URL));
2022-03-07 06:53:18 -08:00
break;
case "About":
showAbout(this.context);
break;
}
}
_backup() async {
2022-09-06 21:04:04 -07:00
final didAuthenticate = await authenticate(context, S.of(context).pleaseAuthenticateToShowAccountSeed);
2022-03-07 06:53:18 -08:00
if (didAuthenticate) {
Navigator.of(context).pushNamed('/backup');
}
}
2022-07-27 01:53:45 -07:00
_keyTool() async {
2022-09-06 21:04:04 -07:00
final didAuthenticate = await authenticate(context, S.of(context).pleaseAuthenticateToShowAccountSeed);
2022-07-27 01:53:45 -07:00
if (didAuthenticate) {
Navigator.of(context).pushNamed('/keytool');
}
}
2022-03-07 06:53:18 -08:00
_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)));
}
2022-07-08 18:51:23 -07:00
_rescan() {
2022-07-21 19:35:21 -07:00
if (syncStatus.paused)
syncStatus.setPause(false);
else if (syncStatus.syncing)
cancelScan(context);
else
rescan(context);
2022-07-08 18:51:23 -07:00
}
2022-03-07 06:53:18 -08:00
_multiPay() {
Navigator.of(context).pushNamed('/multipay');
}
2022-04-13 02:05:05 -07:00
_sign() async {
2022-06-21 17:22:46 -07:00
final String? tx;
2022-06-20 02:05:48 -07:00
if (settings.qrOffline) {
tx = await scanMultiCode(context);
} else {
final res = await FilePicker.platform.pickFiles();
if (res == null) return;
final path = res.files.single.path!;
final file = File(path);
tx = file.readAsStringSync();
}
2022-06-21 17:22:46 -07:00
if (tx != null)
Navigator.of(context).pushNamed('/sign', arguments: tx);
2022-04-13 02:05:05 -07:00
}
2022-03-07 06:53:18 -08:00
_broadcast() async {
2022-06-20 02:05:48 -07:00
String? rawTx;
if (settings.qrOffline) {
rawTx = await scanMultiCode(context);
} else {
final result = await FilePicker.platform.pickFiles();
if (result != null) {
String path = result.files.single.path!;
File f = File(path);
rawTx = f.readAsStringSync();
}
}
2022-03-07 06:53:18 -08:00
2022-06-20 02:05:48 -07:00
if (rawTx != null) {
2022-11-12 19:43:09 -08:00
try {
final res = WarpApi.broadcast(rawTx);
showSnackBar(res);
}
on String catch (e) {
showSnackBar(e);
}
2022-03-07 06:53:18 -08:00
}
}
2022-03-30 22:25:52 -07:00
_ledger() async {
2022-06-07 10:00:08 -07:00
// final result = await FilePicker.platform.pickFiles();
//
// if (result != null) {
// final res = WarpApi.ledgerSign(active.coin, result.files.single.path!);
// final snackBar = SnackBar(content: Text(res));
// rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
// }
2022-03-30 22:25:52 -07:00
}
2022-11-17 01:14:39 -08:00
_convertToWatchOnly() {
WarpApi.convertToWatchOnly(active.coin, active.id);
2022-11-22 01:54:28 -08:00
active.canPay = false;
2022-03-07 06:53:18 -08:00
Navigator.of(context).pop();
}
_settings() {
Navigator.of(context).pushNamed('/settings');
}
_onAddContact() async {
final contact = await contactKey.currentState
2022-04-18 05:15:38 -07:00
?.showContactForm(context, Contact.empty(), false);
2022-03-07 06:53:18 -08:00
if (contact != null) {
contacts.add(contact);
}
}
2022-10-04 23:27:53 -07:00
void _initForegroundTask() {
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
2022-10-12 18:25:29 -07:00
channelId: '${APP_NAME} Sync',
channelName: '${APP_NAME} Foreground Notification',
2022-10-04 23:27:53 -07:00
channelDescription: 'YWallet Synchronization',
channelImportance: NotificationChannelImportance.LOW,
2022-10-12 18:25:29 -07:00
priority: NotificationPriority.HIGH,
2022-10-04 23:27:53 -07:00
iconData: const NotificationIconData(
2022-10-12 18:25:29 -07:00
resType: ResourceType.drawable,
2022-10-04 23:27:53 -07:00
resPrefix: ResourcePrefix.ic,
2022-10-12 18:25:29 -07:00
name: 'notification',
2022-10-04 23:27:53 -07:00
),
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: true,
playSound: false,
),
foregroundTaskOptions: const ForegroundTaskOptions(
isOnceEvent: true,
autoRunOnBoot: false,
allowWakeLock: true,
allowWifiLock: true,
),
);
2022-11-03 06:16:46 -07:00
FlutterForegroundTask.setOnLockScreenVisibility(false);
2022-10-04 23:27:53 -07:00
}
2022-03-07 06:53:18 -08:00
}
2022-06-21 17:22:46 -07:00