import 'dart:math'; import 'dart:ui'; import 'package:currency_text_input_formatter/currency_text_input_formatter.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:intl/intl.dart'; import 'package:path/path.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:sqflite/sqflite.dart'; import 'package:warp_api/warp_api.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'generated/l10n.dart'; import 'account.dart'; import 'account_manager.dart'; import 'backup.dart'; import 'coin/coindef.dart'; import 'multisend.dart'; import 'settings.dart'; import 'restore.dart'; import 'send.dart'; import 'store.dart'; import 'transaction.dart'; var coin = Coin(); const ZECUNIT = 100000000.0; var ZECUNIT_DECIMAL = Decimal.parse('100000000'); const mZECUNIT = 100000; const DEFAULT_FEE = 1000; var accountManager = AccountManager(); var priceStore = PriceStore(); var syncStatus = SyncStatus(); var settings = Settings(); var multipayData = MultiPayStore(); var eta = ETAStore(); var contacts = ContactStore(); Future getDatabase() async { var databasesPath = await getDatabasesPath(); final path = join(databasesPath, 'zec.db'); var db = await openDatabase(path); return db; } void main() { WidgetsFlutterBinding.ensureInitialized(); final home = ZWalletApp(); runApp(FutureBuilder( future: settings.restore(), builder: (context, snapshot) { return snapshot.connectionState == ConnectionState.waiting ? MaterialApp(home: Container()) : Observer(builder: (context) { final theme = settings.themeData.copyWith( dataTableTheme: DataTableThemeData( headingRowColor: MaterialStateColor.resolveWith( (_) => settings.themeData.highlightColor))); return MaterialApp( title: coin.app, theme: theme, home: home, scaffoldMessengerKey: rootScaffoldMessengerKey, localizationsDelegates: [ S.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: S.delegate.supportedLocales, onGenerateRoute: (RouteSettings settings) { var routes = { '/account': (context) => AccountPage(), '/restore': (context) => RestorePage(), '/send': (context) => SendPage(settings.arguments as Contact?), '/accounts': (context) => AccountManagerPage(), '/settings': (context) => SettingsPage(), '/tx': (context) => TransactionPage(settings.arguments as Tx), '/backup': (context) => BackupPage(settings.arguments as int?), '/multipay': (context) => MultiPayPage(), }; return MaterialPageRoute(builder: routes[settings.name]!); }, ); }); })); } class ZWalletApp extends StatefulWidget { @override State createState() => ZWalletAppState(); } class ZWalletAppState extends State { bool initialized = false; Future _init() async { if (!initialized) { initialized = true; final dbPath = await getDatabasesPath(); WarpApi.initWallet(dbPath + "/zec.db", settings.getLWD()); final db = await getDatabase(); await accountManager.init(db); await contacts.init(db); await syncStatus.init(); } return true; } @override Widget build(BuildContext context) { return FutureBuilder( future: _init(), builder: (context, snapshot) { if (!snapshot.hasData) return Container(); return accountManager.accounts.isNotEmpty ? AccountPage() : AccountManagerPage(); }); } } final GlobalKey rootScaffoldMessengerKey = GlobalKey(); List confirmButtons( BuildContext context, VoidCallback? onPressed, {String? okLabel, Icon? okIcon, cancelValue}) { final navigator = Navigator.of(context); return [ ElevatedButton.icon( icon: Icon(Icons.cancel), label: Text(S.of(context).cancel), onPressed: () { cancelValue != null ? navigator.pop(cancelValue) : navigator.pop(); }, style: ElevatedButton.styleFrom( primary: Theme.of(context).buttonTheme.colorScheme!.secondary)), ElevatedButton.icon( icon: okIcon ?? Icon(Icons.done), label: Text(okLabel ?? S.of(context).ok), onPressed: onPressed, ) ]; } List> sampleDaily( List timeseries, int start, int end, int Function(T) getDay, Y Function(T) getY, V Function(V, Y) accFn, V initial) { assert(start % DAY_MS == 0); final s = start ~/ DAY_MS; final e = end ~/ DAY_MS; var acc = initial; var j = 0; while (j < timeseries.length && getDay(timeseries[j]) < s) { acc = accFn(acc, getY(timeseries[j])); j += 1; } List> ts = []; for (var i = s; i <= e; i++) { while (j < timeseries.length && getDay(timeseries[j]) == i) { acc = accFn(acc, getY(timeseries[j])); j += 1; } ts.add(TimeSeriesPoint(i, acc)); } return ts; } String unwrapUA(String address) { final zaddr = WarpApi.getSaplingFromUA(address); return zaddr.isNotEmpty ? zaddr : address; } void showQR(BuildContext context, String text) { showDialog( context: context, barrierColor: Colors.black, builder: (context) => AlertDialog( content: Container( width: double.maxFinite, child: QrImage(data: text, backgroundColor: Colors.white)), )); } Future rescanDialog( BuildContext context, VoidCallback continuation) async { await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text(S.of(context).rescan), content: Text(S.of(context).rescanWalletFromTheFirstBlock), actions: confirmButtons(context, () => confirmWifi(context, continuation)))); } Future confirmWifi(BuildContext context, VoidCallback continuation) async { final connectivity = await Connectivity().checkConnectivity(); if (connectivity == ConnectivityResult.mobile) { Navigator.of(context).pop(); await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text(S.of(context).rescan), content: Text('On Mobile Data, scanning may incur additional charges. Do you want to proceed?'), actions: confirmButtons(context, continuation))); } else continuation(); } Future showMessageBox( BuildContext context, String title, String content, String label) async { final confirm = await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text(title), content: Text(content), actions: confirmButtons(context, () { Navigator.of(context).pop(true); }, okLabel: label, cancelValue: false)), ); return confirm ?? false; } double getScreenSize(BuildContext context) { final size = MediaQuery.of(context).size; return min(size.height, size.width); } Color amountColor(BuildContext context, num a) { final theme = Theme.of(context); if (a < 0) return Colors.red; if (a > 0) return Colors.green; return theme.textTheme.bodyText1!.color!; } TextStyle fontWeight(TextStyle style, num v) { final value = v.abs(); final style2 = style.copyWith(fontFeatures: [FontFeature.tabularFigures()]); if (value >= coin.weights[2]) return style2.copyWith(fontWeight: FontWeight.w800); else if (value >= coin.weights[1]) return style2.copyWith(fontWeight: FontWeight.w600); else if (value >= coin.weights[0]) return style2.copyWith(fontWeight: FontWeight.w400); return style2.copyWith(fontWeight: FontWeight.w200); } CurrencyTextInputFormatter makeInputFormatter(bool mZEC) => CurrencyTextInputFormatter(symbol: '', decimalDigits: precision(mZEC)); double parseNumber(String? s) { if (s == null || s.isEmpty) return 0; return NumberFormat.currency().parse(s).toDouble(); } int precision(bool mZEC) => mZEC ? 3 : 8; Future scanCode(BuildContext context) async { final code = await FlutterBarcodeScanner.scanBarcode('#FF0000', S.of(context).cancel, true, ScanMode.QR); if (code == "-1") return null; return code; } String addressLeftTrim(String address) => address != "" ? address.substring(0, 8) + "..." + address.substring(address.length - 16) : ""; void showSnackBar(String msg) { final snackBar = SnackBar(content: Text(msg)); rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar); } enum DeviceWidth { xs, sm, md, lg, xl } DeviceWidth getWidth(BuildContext context) { final width = MediaQuery.of(context).size.width; if (width < 600) return DeviceWidth.xs; if (width < 960) return DeviceWidth.sm; if (width < 1280) return DeviceWidth.md; if (width < 1920) return DeviceWidth.lg; return DeviceWidth.xl; } String decimalFormat(double x, int decimalDigits, { String symbol = '' }) => NumberFormat.currency(decimalDigits: decimalDigits, symbol: symbol).format(x).trimRight();