diff --git a/assets/translations/en.json b/assets/translations/en.json index 1a6eb581..8816d396 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -49,8 +49,12 @@ "app_settings_revealAuthButton": "Reveal authentication options", "app_settings_seed": "Seed phrase", "app_settings_shareSeed": "Share seed", + "app_settings_auth_header": "Authentication", + "app_settings_appbar": "App Settings", + "app_settings_changeCode": "Change PIN", "receive_requested_amount": "Requested amount", "receive_enter_amount": "Please enter an amount", + "receive_share": "Share", "send_address": "Address", "send_enter_address": "Please enter an address", "send_invalid_address": "Invalid address", @@ -78,5 +82,13 @@ "authenticate_biometric_hint" : "Verify identity", "authenticate_biometric_title" : "Authentication required", "authenticate_title" : "Please enter your PIN", - "authenticate_confirm_title" : "Re-enter your PIN" + "authenticate_title_new" : "Please enter your new PIN", + "authenticate_confirm_title" : "Re-enter your PIN", + "authenticate_confirm_title_new" : "Re-enter your new PIN", + "authenticate_change_pin_success" : "PIN succesfully changed", + "wallet_scan_appBar_title": "Scanning your imported seed", + "wallet_scan_notice": "This might take a while. Don't close this screen!", + "wallet_bottom_nav_receive": "Receive", + "wallet_bottom_nav_tx": "Transactions", + "wallet_bottom_nav_send": "Send" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 74b0124e..659369d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -71,7 +71,6 @@ void main() async { runApp(MyApp()); } - class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/lib/providers/activewallets.dart b/lib/providers/activewallets.dart index 98a9bf60..1caf7a49 100644 --- a/lib/providers/activewallets.dart +++ b/lib/providers/activewallets.dart @@ -104,7 +104,7 @@ class ActiveWallets with ChangeNotifier { //wallet is not brand new, lets find an unused address var unusedAddr; openWallet.addresses.forEach((walletAddr) { - if (walletAddr.used == false) { + if (walletAddr.used == false && walletAddr.status == null) { unusedAddr = walletAddr.address; } }); @@ -213,90 +213,108 @@ class ActiveWallets with ChangeNotifier { notifyListeners(); } - Future putTx(String identifier, String address, Map tx) async { + Future putTx(String identifier, String address, Map tx, + [bool scanMode = false]) async { CoinWallet openWallet = getSpecificCoinWallet(identifier); // log("$address puttx: $tx"); - //check if that tx is already in the db - List txInWallet = openWallet.transactions; - bool isInWallet = false; - txInWallet.forEach((walletTx) { - if (walletTx.txid == tx["txid"]) { - isInWallet = true; - if (isInWallet == true) { - if (walletTx.timestamp == null) { - //did the tx confirm? - walletTx.newTimestamp = tx["blocktime"]; - } - if (tx["confirmations"] != null && - walletTx.confirmations < tx["confirmations"]) { - //more confirmations? - walletTx.newConfirmations = tx["confirmations"]; + if (scanMode == true) { + //write phantom tx that are not displayed in tx list but known to the wallet + //so they won't be parsed again and cause weird display behaviour + openWallet.putTransaction(WalletTransaction( + txid: tx["txid"], + timestamp: -1, //flags phantom tx + value: 0, + fee: 0, + address: address, + direction: "in", + broadCasted: true, + confirmations: 0, + broadcastHex: "", + )); + } else { + //check if that tx is already in the db + List txInWallet = openWallet.transactions; + bool isInWallet = false; + txInWallet.forEach((walletTx) { + if (walletTx.txid == tx["txid"]) { + isInWallet = true; + if (isInWallet == true) { + if (walletTx.timestamp == null) { + //did the tx confirm? + walletTx.newTimestamp = tx["blocktime"]; + } + if (tx["confirmations"] != null && + walletTx.confirmations < tx["confirmations"]) { + //more confirmations? + walletTx.newConfirmations = tx["confirmations"]; + } } } - } - }); - //it's not in wallet yet - if (!isInWallet) { - //check if that tx addresses more than one of our addresses - var utxoInWallet = openWallet.utxos - .firstWhere((elem) => elem.hash == tx["txid"], orElse: () => null); - String direction = utxoInWallet == null ? "out" : "in"; - - if (direction == "in") { - List voutList = tx["vout"].toList(); - voutList.forEach((vOut) { - final asMap = vOut as Map; - asMap["scriptPubKey"]["addresses"].forEach((addr) { - if (openWallet.addresses.firstWhere( - (element) => element.address == addr, - orElse: () => null) != - null) { - //address is ours, add new tx - final txValue = (vOut["value"] * 1000000).toInt(); - - openWallet.putTransaction(WalletTransaction( - txid: tx["txid"], - timestamp: tx["blocktime"], - value: txValue, - fee: 0, - address: addr, - direction: direction, - broadCasted: true, - confirmations: tx["confirmations"] ?? 0, - broadcastHex: "", - )); - } + }); + //it's not in wallet yet + if (!isInWallet) { + //check if that tx addresses more than one of our addresses + var utxoInWallet = openWallet.utxos + .firstWhere((elem) => elem.hash == tx["txid"], orElse: () => null); + String direction = utxoInWallet == null ? "out" : "in"; + + if (direction == "in") { + List voutList = tx["vout"].toList(); + voutList.forEach((vOut) { + final asMap = vOut as Map; + asMap["scriptPubKey"]["addresses"].forEach((addr) { + if (openWallet.addresses.firstWhere( + (element) => element.address == addr, + orElse: () => null) != + null) { + //address is ours, add new tx + final txValue = (vOut["value"] * 1000000).toInt(); + + openWallet.putTransaction(WalletTransaction( + txid: tx["txid"], + timestamp: tx["blocktime"], + value: txValue, + fee: 0, + address: addr, + direction: direction, + broadCasted: true, + confirmations: tx["confirmations"] ?? 0, + broadcastHex: "", + )); + } + }); }); - }); - } else { - //outgoing tx - openWallet.putTransaction(WalletTransaction( - txid: tx["txid"], - timestamp: tx["blocktime"], - value: tx["outValue"], - fee: tx["outFees"], - address: address, - direction: direction, - broadCasted: false, - confirmations: 0, - broadcastHex: tx["hex"], - )); - } + } else { + //outgoing tx + openWallet.putTransaction(WalletTransaction( + txid: tx["txid"], + timestamp: tx["blocktime"], + value: tx["outValue"], + fee: tx["outFees"], + address: address, + direction: direction, + broadCasted: false, + confirmations: 0, + broadcastHex: tx["hex"], + )); + } - // trigger notification - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - - if (direction == "in") - await flutterLocalNotificationsPlugin.show( - 0, - 'New transaction received', - tx["txid"], - LocalNotificationSettings.platformChannelSpecifics, - payload: identifier, - ); + // trigger notification + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + if (direction == "in") + await flutterLocalNotificationsPlugin.show( + 0, + 'New transaction received', + tx["txid"], + LocalNotificationSettings.platformChannelSpecifics, + payload: identifier, + ); + } } + notifyListeners(); await openWallet.save(); } diff --git a/lib/providers/electrumconnection.dart b/lib/providers/electrumconnection.dart index cb31846d..70c14844 100644 --- a/lib/providers/electrumconnection.dart +++ b/lib/providers/electrumconnection.dart @@ -29,12 +29,14 @@ class ElectrumConnection with ChangeNotifier { ElectrumConnection(this._activeWallets); int _latestBlock; bool _closedIntentionally = false; + bool _scanMode = false; - bool init(walletName) { + bool init(walletName, [bool scanMode = false]) { if (_connection == null) { _coinName = walletName; _connectionState = "waiting"; _closedIntentionally = false; + _scanMode = scanMode; print("init server connection"); connect(); Stream stream = _connection.stream; @@ -104,6 +106,7 @@ class ElectrumConnection with ChangeNotifier { _connection = null; _addresses = {}; _latestBlock = null; + _scanMode = false; if (_closedIntentionally == false) Timer(Duration(seconds: 10), () => init(_coinName)); //retry if not intentional @@ -210,25 +213,19 @@ class ElectrumConnection with ChangeNotifier { void handleScriptHashSubscribeNotification( String hashId, String newStatus) async { - //got update notification for hash => get utxo and history list + //got update notification for hash => get utxo final address = _addresses.keys.firstWhere( (element) => _addresses[element] == hashId, orElse: () => null); print("update for $hashId"); //update status so we flag that we proccessed this update already await _activeWallets.updateAddressStatus(_coinName, address, newStatus); - //fire listunspent + //fire listunspent to get utxo sendMessage( "blockchain.scripthash.listunspent", "utxo_$address", [hashId], ); - //fire get_history - sendMessage( - "blockchain.scripthash.get_history", - "history_$address", - [hashId], - ); } void handleUtxo(String id, List utxos) async { @@ -238,6 +235,12 @@ class ElectrumConnection with ChangeNotifier { txAddr, utxos, ); + //fire get_history + sendMessage( + "blockchain.scripthash.get_history", + "history_$txAddr", + [_addresses[txAddr]], + ); } void handleHistory(List result) async { @@ -271,7 +274,7 @@ class ElectrumConnection with ChangeNotifier { String txId = id.replaceFirst("tx_", ""); String addr = await _activeWallets.getAddressForTx(_coinName, txId); if (tx != null) { - await _activeWallets.putTx(_coinName, addr, tx); + await _activeWallets.putTx(_coinName, addr, tx, _scanMode); } } diff --git a/lib/providers/encryptedbox.dart b/lib/providers/encryptedbox.dart index 2f85548d..13075e74 100644 --- a/lib/providers/encryptedbox.dart +++ b/lib/providers/encryptedbox.dart @@ -31,12 +31,10 @@ class EncryptedBox with ChangeNotifier { return _passCode; } - Future setPassCode(passCode) async { - if (_passCode == null) { - await _secureStorage.write(key: "passCode", value: passCode); - return true; - } - return false; + Future setPassCode(String passCode) async { + await _secureStorage.write(key: "passCode", value: passCode); + _passCode = passCode; + return true; } Future getGenericBox(String name) async { diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index cab21462..7e0ea7fb 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -1,6 +1,9 @@ import "package:flutter/material.dart"; +import 'package:flutter_screen_lock/functions.dart'; +import 'package:flutter_screen_lock/heading_title.dart'; import 'package:peercoin/providers/activewallets.dart'; import 'package:peercoin/providers/appsettings.dart'; +import 'package:peercoin/providers/encryptedbox.dart'; import 'package:peercoin/tools/app_localizations.dart'; import 'package:peercoin/tools/auth.dart'; import 'package:peercoin/widgets/app_drawer.dart'; @@ -58,6 +61,38 @@ class _AppSettingsScreenState extends State { ); } + void changePIN(bool biometricsAllowed) async { + await Auth.requireAuth( + context, + biometricsAllowed, + () async => await screenLock( + title: HeadingTitle( + text: + AppLocalizations.instance.translate("authenticate_title_new")), + confirmTitle: HeadingTitle( + text: AppLocalizations.instance + .translate("authenticate_confirm_title_new")), + context: context, + correctString: '', + digits: 6, + confirmation: true, + didConfirmed: (matchedText) async { + await Provider.of(context, listen: false) + .setPassCode(matchedText); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + AppLocalizations.instance + .translate("authenticate_change_pin_success"), + textAlign: TextAlign.center, + ), + duration: Duration(seconds: 2), + )); + Navigator.of(context).pop(); + }, + ), + ); + } + @override Widget build(BuildContext context) { _settings = context.watch(); @@ -66,7 +101,9 @@ class _AppSettingsScreenState extends State { return Scaffold( appBar: AppBar( - title: const Text("App Settings"), + title: Text( + AppLocalizations.instance.translate('app_settings_appbar'), + ), ), drawer: AppDrawer(), body: SingleChildScrollView( @@ -75,7 +112,7 @@ class _AppSettingsScreenState extends State { child: Column( children: [ Text( - "Authentification", + AppLocalizations.instance.translate('app_settings_auth_header'), style: TextStyle(fontWeight: FontWeight.bold), ), SizedBox(height: 10), @@ -138,6 +175,15 @@ class _AppSettingsScreenState extends State { _settings.setAuthenticationOptions( "newWallet", newState); }), + TextButton( + onPressed: () => changePIN(_settings.biometricsAllowed), + child: Text( + AppLocalizations.instance + .translate('app_settings_changeCode'), + style: + TextStyle(color: Theme.of(context).primaryColor), + ), + ) ]), Divider(), SizedBox(height: 10), diff --git a/lib/screens/new_wallet.dart b/lib/screens/new_wallet.dart index bad2ddcd..139bb6ef 100644 --- a/lib/screens/new_wallet.dart +++ b/lib/screens/new_wallet.dart @@ -1,15 +1,16 @@ import 'package:flutter/cupertino.dart'; import "package:flutter/material.dart"; import 'package:peercoin/providers/appsettings.dart'; +import 'package:peercoin/providers/unencryptedOptions.dart'; import 'package:peercoin/tools/app_localizations.dart'; import 'package:peercoin/models/availablecoins.dart'; import 'package:peercoin/models/coin.dart'; import 'package:peercoin/providers/activewallets.dart'; +import 'package:peercoin/tools/app_routes.dart'; import 'package:peercoin/tools/auth.dart'; import 'package:provider/provider.dart'; class NewWalletScreen extends StatefulWidget { - @override _NewWalletScreenState createState() => _NewWalletScreenState(); } @@ -24,7 +25,16 @@ class _NewWalletScreenState extends State { try { await Provider.of(context, listen: false).addWallet(_coin, availableCoins[_coin].displayName, availableCoins[_coin].letterCode); - Navigator.of(context).pop(); + var prefs = + await Provider.of(context, listen: false).prefs; + + if (prefs.getBool("importedSeed") == true) { + await Navigator.of(context).pushNamedAndRemoveUntil( + Routes.WalletImportScan, (_) => false, + arguments: _coin); + } else { + Navigator.of(context).pop(); + } } catch (e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( diff --git a/lib/screens/setup_import_seed.dart b/lib/screens/setup_import_seed.dart index 0042a790..bde16734 100644 --- a/lib/screens/setup_import_seed.dart +++ b/lib/screens/setup_import_seed.dart @@ -1,18 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:peercoin/providers/activewallets.dart'; +import 'package:peercoin/providers/unencryptedOptions.dart'; import 'package:peercoin/tools/app_localizations.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:peercoin/tools/app_routes.dart'; import 'package:peercoin/widgets/loading_indicator.dart'; import 'package:provider/provider.dart'; -class SetupImportSeed extends StatefulWidget { +class SetupImportSeedScreen extends StatefulWidget { @override _SetupImportSeedState createState() => _SetupImportSeedState(); } -class _SetupImportSeedState extends State { +class _SetupImportSeedState extends State { var _controller = TextEditingController(); final _formKey = GlobalKey(); bool _loading = false; @@ -25,6 +26,9 @@ class _SetupImportSeedState extends State { Provider.of(context, listen: false); await _activeWallets.init(); await _activeWallets.createPhrase(_controller.text); + var prefs = + await Provider.of(context, listen: false).prefs; + await prefs.setBool("importedSeed", true); await Navigator.of(context).popAndPushNamed(Routes.SetUpPin); } @@ -76,8 +80,8 @@ class _SetupImportSeedState extends State { onPressed: () async { ClipboardData data = await Clipboard.getData('text/plain'); - _controller.text = data.text; + FocusScope.of(context).unfocus(); //hide keyboard }, icon: Icon(Icons.paste, color: Theme.of(context).primaryColor), diff --git a/lib/screens/setup_pin_code.dart b/lib/screens/setup_pin_code.dart index 899f3ce7..9981fe35 100644 --- a/lib/screens/setup_pin_code.dart +++ b/lib/screens/setup_pin_code.dart @@ -62,12 +62,12 @@ class _SetupPinCodeScreenState extends State { digits: 6, confirmation: true, didConfirmed: (matchedText) async { - Provider.of(context, listen: false) + await Provider.of(context, listen: false) .setPassCode(matchedText); AppSettings settings = Provider.of(context, listen: false); - settings.createInitialSettings(_biometricsAllowed); + await settings.createInitialSettings(_biometricsAllowed); var prefs = await Provider.of(context, listen: false) diff --git a/lib/screens/setup_save_seed.dart b/lib/screens/setup_save_seed.dart index a8e8c5ff..b692f464 100644 --- a/lib/screens/setup_save_seed.dart +++ b/lib/screens/setup_save_seed.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:peercoin/providers/unencryptedOptions.dart'; import 'package:peercoin/tools/app_localizations.dart'; import 'package:peercoin/providers/activewallets.dart'; import 'package:peercoin/tools/app_routes.dart'; @@ -77,7 +78,11 @@ class _SetupSaveScreenState extends State { SizedBox(height: 30), sharedYet ? TextButton( - onPressed: () { + onPressed: () async { + var prefs = await Provider.of(context, + listen: false) + .prefs; + await prefs.setBool("importedSeed", false); Navigator.popAndPushNamed(context, Routes.SetUpPin); }, child: Text( diff --git a/lib/screens/transaction_details.dart b/lib/screens/transaction_details.dart index 8d57ee4e..2338128b 100644 --- a/lib/screens/transaction_details.dart +++ b/lib/screens/transaction_details.dart @@ -7,7 +7,6 @@ import 'package:peercoin/models/wallettransaction.dart'; import 'package:url_launcher/url_launcher.dart'; class TransactionDetails extends StatelessWidget { - void _launchURL(_url) async { await canLaunch(_url) ? await launch( @@ -111,8 +110,7 @@ class TransactionDetails extends StatelessWidget { color: Theme.of(context).primaryColor, ), label: Text( - AppLocalizations.instance - .translate('tx_view_in_explorer', null), + AppLocalizations.instance.translate('tx_view_in_explorer'), style: TextStyle(color: Theme.of(context).primaryColor), )), ) diff --git a/lib/screens/wallet_home.dart b/lib/screens/wallet_home.dart index ccf0385b..def615bb 100644 --- a/lib/screens/wallet_home.dart +++ b/lib/screens/wallet_home.dart @@ -52,7 +52,7 @@ class _WalletHomeState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - _connectionProvider.init(_wallet.name); + _connectionProvider.init(_wallet.name, false); } } @@ -66,7 +66,7 @@ class _WalletHomeState extends State _walletTransactions = await _activeWallets.getWalletTransactions(_wallet.name); - if (_connectionProvider.init(_wallet.name)) { + if (_connectionProvider.init(_wallet.name, false)) { _connectionProvider.subscribeToScriptHashes( await _activeWallets.getWalletScriptHashes(_wallet.name)); rebroadCastUnsendTx(); @@ -102,8 +102,8 @@ class _WalletHomeState extends State print("new block ${_connectionProvider.latestBlock}"); _latestBlock = _connectionProvider.latestBlock; - var unconfirmedTx = - _walletTransactions.where((element) => element.confirmations < 6); + var unconfirmedTx = _walletTransactions.where((element) => + element.confirmations < 6 && element.timestamp != -1); unconfirmedTx.forEach((element) { print("requesting update for ${element.txid}"); _connectionProvider.requestTxUpdate(element.txid); @@ -143,15 +143,17 @@ class _WalletHomeState extends State items: [ BottomNavigationBarItem( icon: Icon(Icons.arrow_left), - label: 'Receive', + label: AppLocalizations.instance + .translate('wallet_bottom_nav_receive'), ), BottomNavigationBarItem( icon: Icon(Icons.list), - label: 'Transactions', + label: AppLocalizations.instance.translate('wallet_bottom_nav_tx'), ), BottomNavigationBarItem( icon: Icon(Icons.arrow_right), - label: 'Send', + label: + AppLocalizations.instance.translate('wallet_bottom_nav_send'), ) ], ), @@ -267,7 +269,7 @@ class _WalletHomeState extends State ), Text( AppLocalizations.instance - .translate('wallet_connected', null), + .translate('wallet_connected'), style: TextStyle( color: Theme.of(context).accentColor, fontSize: 12), diff --git a/lib/screens/wallet_import_scan.dart b/lib/screens/wallet_import_scan.dart new file mode 100644 index 00000000..89aedb4c --- /dev/null +++ b/lib/screens/wallet_import_scan.dart @@ -0,0 +1,104 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:peercoin/providers/activewallets.dart'; +import 'package:peercoin/providers/electrumconnection.dart'; +import 'package:peercoin/tools/app_localizations.dart'; +import 'package:peercoin/tools/app_routes.dart'; +import 'package:peercoin/widgets/loading_indicator.dart'; +import 'package:provider/provider.dart'; + +class WalletImportScanScreen extends StatefulWidget { + @override + _WalletImportScanScreenState createState() => _WalletImportScanScreenState(); +} + +class _WalletImportScanScreenState extends State { + bool _initial = true; + ElectrumConnection _connectionProvider; + ActiveWallets _activeWallets; + String _coinName = ""; + String _unusedAddress = ""; + Iterable _listenedAddresses; + String _connectionState = ""; + int _latestUpdate = 0; + Timer _timer; + + @override + void didChangeDependencies() async { + if (_initial == true) { + _coinName = ModalRoute.of(context).settings.arguments as String; + _connectionProvider = Provider.of(context); + _activeWallets = Provider.of(context); + await _activeWallets.generateUnusedAddress(_coinName); + + if (_connectionProvider.init(_coinName, true)) { + _connectionProvider.subscribeToScriptHashes( + await _activeWallets.getWalletScriptHashes(_coinName)); + } + + _timer = Timer.periodic(Duration(seconds: 5), (timer) { + int dueTime = DateTime.now().millisecondsSinceEpoch ~/ 1000 + 5; + if (_latestUpdate <= dueTime) { + Navigator.of(context).pushReplacementNamed(Routes.WalletList); + } + }); + setState(() { + _initial = false; + }); + } else if (_connectionProvider != null) { + _connectionState = _connectionProvider.connectionState; + _unusedAddress = _activeWallets.getUnusedAddress; + + _listenedAddresses = _connectionProvider.listenedAddresses.keys; + if (_connectionState == "connected") { + if (_listenedAddresses.length == 0) { + //listenedAddresses not populated after reconnect - resubscribe + _connectionProvider.subscribeToScriptHashes( + await _activeWallets.getWalletScriptHashes(_coinName)); + } else if (_listenedAddresses.contains(_unusedAddress) == false) { + //subscribe to newly created addresses + _connectionProvider.subscribeToScriptHashes(await _activeWallets + .getWalletScriptHashes(_coinName, _unusedAddress)); + } + _latestUpdate = DateTime.now().millisecondsSinceEpoch ~/ 1000; + } + } + + super.didChangeDependencies(); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + + @override + void deactivate() { + _connectionProvider.closeConnection(); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Center( + child: Text( + AppLocalizations.instance.translate('wallet_scan_appBar_title'), + )), + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LoadingIndicator(), + SizedBox(height: 20), + Text( + AppLocalizations.instance.translate('wallet_scan_notice'), + ) + ], + ), + ); + } +} diff --git a/lib/screens/wallet_list.dart b/lib/screens/wallet_list.dart index 345b137b..7cd06207 100644 --- a/lib/screens/wallet_list.dart +++ b/lib/screens/wallet_list.dart @@ -76,7 +76,7 @@ class _WalletListScreenState extends State { return Column(children: [ SizedBox(height: 30), Text(AppLocalizations.instance - .translate('wallets_none', null)), + .translate('wallets_none')), SizedBox(height: 30) ]); } diff --git a/lib/tools/app_routes.dart b/lib/tools/app_routes.dart index 213001ac..7c741b0f 100644 --- a/lib/tools/app_routes.dart +++ b/lib/tools/app_routes.dart @@ -7,6 +7,7 @@ import 'package:peercoin/screens/setup_pin_code.dart'; import 'package:peercoin/screens/setup_save_seed.dart'; import 'package:peercoin/screens/transaction_details.dart'; import 'package:peercoin/screens/wallet_home.dart'; +import 'package:peercoin/screens/wallet_import_scan.dart'; import 'package:peercoin/screens/wallet_list.dart'; class Routes { @@ -20,6 +21,7 @@ class Routes { static const String SetupImport = "/setup-import-seed"; static const String Transaction = "/tx-detail"; static const String WalletHome = "/wallet-home"; + static const String WalletImportScan = "/wallet-import-scan"; static Map getRoutes() { return { @@ -31,7 +33,8 @@ class Routes { Routes.QRScan: (context) => QRScanner(), Routes.Transaction: (context) => TransactionDetails(), Routes.AppSettings: (context) => AppSettingsScreen(), - Routes.SetupImport: (context) => SetupImportSeed() + Routes.SetupImport: (context) => SetupImportSeedScreen(), + Routes.WalletImportScan: (context) => WalletImportScanScreen(), }; } } diff --git a/lib/tools/auth.dart b/lib/tools/auth.dart index c9341dc2..d1c79fc2 100644 --- a/lib/tools/auth.dart +++ b/lib/tools/auth.dart @@ -14,6 +14,8 @@ class Auth { Navigator.pop(context); await callback(); //TODO having a loading animation here would be nicer + } else { + Navigator.pop(context); } } diff --git a/lib/widgets/receive_tab.dart b/lib/widgets/receive_tab.dart index b1b6e100..1446b1b8 100644 --- a/lib/widgets/receive_tab.dart +++ b/lib/widgets/receive_tab.dart @@ -5,6 +5,7 @@ import 'package:peercoin/models/availablecoins.dart'; import 'package:peercoin/models/coin.dart'; import 'package:peercoin/models/coinwallet.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import 'package:share/share.dart'; class ReceiveTab extends StatefulWidget { final _unusedAddress; @@ -43,13 +44,17 @@ class _ReceiveTabState extends State { }); } - - RegExp getValidator(int fractions){ - String expression = r'^([1-9]{1}[0-9]{0,' + fractions.toString() + - r'}(,[0-9]{3})*(.[0-9]{0,'+ fractions.toString() + - r'})?|[1-9]{1}[0-9]{0,}(.[0-9]{0,'+ fractions.toString() + - r'})?|0(.[0-9]{0,'+ fractions.toString() + - r'})?|(.[0-9]{1,'+ fractions.toString() + + RegExp getValidator(int fractions) { + String expression = r'^([1-9]{1}[0-9]{0,' + + fractions.toString() + + r'}(,[0-9]{3})*(.[0-9]{0,' + + fractions.toString() + + r'})?|[1-9]{1}[0-9]{0,}(.[0-9]{0,' + + fractions.toString() + + r'})?|0(.[0-9]{0,' + + fractions.toString() + + r'})?|(.[0-9]{1,' + + fractions.toString() + r'})?)$'; return new RegExp(expression); @@ -62,96 +67,98 @@ class _ReceiveTabState extends State { padding: EdgeInsets.all(20), child: Form( key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - InkWell( - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return SimpleDialog(children: [ - Center( - child: SizedBox( - - height: MediaQuery.of(context).size.height * 0.33, - width: MediaQuery.of(context).size.width * 1, - child: Center( - child: QrImage( - backgroundColor: Colors.white, - foregroundColor: Colors.black, - data: _qrString ?? widget._unusedAddress, - ), + child: + Column(crossAxisAlignment: CrossAxisAlignment.center, children: < + Widget>[ + InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return SimpleDialog(children: [ + Center( + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.33, + width: MediaQuery.of(context).size.width * 1, + child: Center( + child: QrImage( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + data: _qrString ?? widget._unusedAddress, ), ), - ) - ]); - }, - ); - }, - child: QrImage( - data: _qrString ?? widget._unusedAddress, - size: MediaQuery.of(context).size.width * 0.3, - padding: EdgeInsets.all(1), - backgroundColor: Colors.white, - foregroundColor: Colors.black, - ), + ), + ) + ]); + }, + ); + }, + child: QrImage( + data: _qrString ?? widget._unusedAddress, + size: MediaQuery.of(context).size.width * 0.3, + padding: EdgeInsets.all(1), + backgroundColor: Colors.white, + foregroundColor: Colors.black, ), - SizedBox(height: 20), - Container( - decoration: BoxDecoration( - color: Theme.of(context).accentColor, - borderRadius: BorderRadius.all(Radius.circular(4))), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: FittedBox( - child: SelectableText( - widget._unusedAddress, - style: TextStyle(color: Colors.white), - ), + ), + SizedBox(height: 20), + Container( + decoration: BoxDecoration( + color: Theme.of(context).accentColor, + borderRadius: BorderRadius.all(Radius.circular(4))), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: FittedBox( + child: SelectableText( + widget._unusedAddress, + style: TextStyle(color: Colors.white), ), ), ), - SizedBox(height: 10), - TextFormField( - textInputAction: TextInputAction.done, - key: _amountKey, - controller: amountController, - onChanged: (String newString) { - double parsed = - newString != "" ? double.parse(newString) : 0; - if (parsed > 0) { - stringBuilder(parsed); - } else { - stringBuilder(0); - } - }, - autocorrect: false, - inputFormatters: [ - FilteringTextInputFormatter.allow( - getValidator(_availableCoin.fractions) - ), - ], - keyboardType: TextInputType.numberWithOptions(signed: true), - decoration: InputDecoration( - icon: Icon(Icons.money), - labelText: AppLocalizations.instance - .translate('receive_requested_amount'), - suffix: Text(_wallet.letterCode), - ), - validator: (value) { - if (value.isEmpty) { - return AppLocalizations.instance - .translate('receive_enter_amount'); - } - return null; - }), - SizedBox(height: 30), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: []), - ], - ), + ), + SizedBox(height: 20), + TextFormField( + textInputAction: TextInputAction.done, + key: _amountKey, + controller: amountController, + onChanged: (String newString) { + double parsed = newString != "" ? double.parse(newString) : 0; + if (parsed > 0) { + stringBuilder(parsed); + } else { + stringBuilder(0); + } + }, + autocorrect: false, + inputFormatters: [ + FilteringTextInputFormatter.allow( + getValidator(_availableCoin.fractions)), + ], + keyboardType: TextInputType.numberWithOptions(signed: true), + decoration: InputDecoration( + icon: Icon(Icons.money), + labelText: AppLocalizations.instance + .translate('receive_requested_amount'), + suffix: Text(_wallet.letterCode), + ), + validator: (value) { + if (value.isEmpty) { + return AppLocalizations.instance + .translate('receive_enter_amount'); + } + return null; + }), + SizedBox(height: 20), + TextButton.icon( + onPressed: () async { + await Share.share(_qrString ?? widget._unusedAddress); + }, + icon: Icon(Icons.share, color: Theme.of(context).primaryColor), + label: Text( + AppLocalizations.instance.translate('receive_share'), + style: TextStyle(color: Theme.of(context).primaryColor), + )) + ]), ), ), ); diff --git a/lib/widgets/transactions_list.dart b/lib/widgets/transactions_list.dart index d01f16dc..fc635ac0 100644 --- a/lib/widgets/transactions_list.dart +++ b/lib/widgets/transactions_list.dart @@ -24,8 +24,11 @@ class _TransactionListState extends State { @override Widget build(BuildContext context) { - List _reversedTx = - widget._walletTransactions.reversed.toList(); + List _reversedTx = widget._walletTransactions + .where((element) => element.timestamp != -1) //filter phatom tx + .toList() + .reversed + .toList(); List _filteredTx = _reversedTx; if (_filterChoice != "all") { _filteredTx = _reversedTx @@ -35,39 +38,39 @@ class _TransactionListState extends State { return Expanded( child: Column(children: [ - if (widget._walletTransactions.length > 0) + if (_reversedTx.length > 0) Wrap( spacing: 8.0, children: [ ChoiceChip( visualDensity: VisualDensity(horizontal: 0.0, vertical: -4), label: Container( - child: Text(AppLocalizations.instance - .translate('transactions_in', null))), + child: Text( + AppLocalizations.instance.translate('transactions_in'))), selected: _filterChoice == "in", onSelected: (_) => _handleSelect("in"), ), ChoiceChip( visualDensity: VisualDensity(horizontal: 0.0, vertical: -4), - label: Text(AppLocalizations.instance - .translate('transactions_all', null)), + label: + Text(AppLocalizations.instance.translate('transactions_all')), selected: _filterChoice == "all", onSelected: (_) => _handleSelect("all"), ), ChoiceChip( visualDensity: VisualDensity(horizontal: 0.0, vertical: -4), - label: Text(AppLocalizations.instance - .translate('transactions_out', null)), + label: + Text(AppLocalizations.instance.translate('transactions_out')), selected: _filterChoice == "out", onSelected: (_) => _handleSelect("out"), ), ], ), Expanded( - child: widget._walletTransactions.length == 0 + child: _reversedTx.length == 0 ? Center( - child: Text(AppLocalizations.instance - .translate('transactions_none', null)), + child: Text( + AppLocalizations.instance.translate('transactions_none')), ) : GestureDetector( onHorizontalDragEnd: (dragEndDetails) { diff --git a/pubspec.yaml b/pubspec.yaml index e468b8c1..79f26c8c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.1.5+1 +version: 0.1.6+1 environment: sdk: ">=2.7.0 <3.0.0"