Merge branch '4.0.8' into CAKE-158-transaction-and-balance-displaying-between-accounts

# Conflicts:
#	lib/src/screens/receive/receive_page.dart
This commit is contained in:
OleksandrSobol 2020-11-18 18:19:43 +02:00
commit 17ba74bce7
17 changed files with 187 additions and 93 deletions

View file

@ -1,5 +1,5 @@
# Cake Wallet # Cake Wallet
The project description, motivation, build scripts, instructions, tests will be added soon (Spring 2020); The project description, motivation, build scripts, instructions, tests will be added soon (Spring 202X);
Copyright (c) 2020 Cake Technologies LLC. Copyright (c) 2020 Cake Technologies LLC.

View file

@ -6,6 +6,8 @@
<application <application
android:label="Cake Wallet" android:label="Cake Wallet"
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View file

@ -371,7 +371,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 4.0.3; MARKETING_VERSION = 4.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -511,7 +511,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 4.0.3; MARKETING_VERSION = 4.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -545,7 +545,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 4.0.3; MARKETING_VERSION = 4.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View file

@ -204,7 +204,8 @@ Future setup(
authPageState.changeProcessText('Loading the wallet'); authPageState.changeProcessText('Loading the wallet');
if (loginError != null) { if (loginError != null) {
authPageState.changeProcessText('ERROR: ${loginError.toString()}'); authPageState
.changeProcessText('ERROR: ${loginError.toString()}');
} }
ReactionDisposer _reaction; ReactionDisposer _reaction;

View file

@ -19,3 +19,10 @@ Future<String> pathForWalletDir({@required String name, @required WalletType ty
Future<String> pathForWallet({@required String name, @required WalletType type}) async => Future<String> pathForWallet({@required String name, @required WalletType type}) async =>
await pathForWalletDir(name: name, type: type) await pathForWalletDir(name: name, type: type)
.then((path) => path + '/$name'); .then((path) => path + '/$name');
Future<String> outdatedAndroidPathForWalletDir({String name}) async {
final directory = await getApplicationDocumentsDirectory();
final pathDir = directory.path + '/$name';
return pathDir;
}

View file

@ -20,7 +20,7 @@ abstract class MoneroAccountListBase with Store {
bool _isRefreshing; bool _isRefreshing;
bool _isUpdating; bool _isUpdating;
Future update() async { void update() async {
if (_isUpdating) { if (_isUpdating) {
return; return;
} }

View file

@ -125,8 +125,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
_onAccountChangeReaction?.reaction?.dispose(); _onAccountChangeReaction?.reaction?.dispose();
} }
Future<bool> validate() async { bool validate() {
await accountList.update(); accountList.update();
final accountListLength = accountList.accounts?.length ?? 0; final accountListLength = accountList.accounts?.length ?? 0;
if (accountListLength <= 0) { if (accountListLength <= 0) {

View file

@ -27,7 +27,7 @@ class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
class MoneroWalletLoadingException implements Exception { class MoneroWalletLoadingException implements Exception {
@override @override
String toString() => 'The wallet is damaged.'; String toString() => 'Failure to load the wallet.';
} }
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials { class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
@ -93,6 +93,11 @@ class MoneroWalletService extends WalletService<
Future<MoneroWallet> openWallet(String name, String password) async { Future<MoneroWallet> openWallet(String name, String password) async {
try { try {
final path = await pathForWallet(name: name, type: WalletType.monero); final path = await pathForWallet(name: name, type: WalletType.monero);
if (!File(path).existsSync()) {
await repairOldAndroidWallet(name);
}
await monero_wallet_manager await monero_wallet_manager
.openWalletAsync({'path': path, 'password': password}); .openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values.firstWhere( final walletInfo = walletInfoSource.values.firstWhere(
@ -100,17 +105,17 @@ class MoneroWalletService extends WalletService<
orElse: () => null); orElse: () => null);
final wallet = MoneroWallet( final wallet = MoneroWallet(
filename: monero_wallet.getFilename(), walletInfo: walletInfo); filename: monero_wallet.getFilename(), walletInfo: walletInfo);
final isValid = await wallet.validate(); final isValid = wallet.validate();
if (!isValid) { if (!isValid) {
// if (wallet.seed?.isNotEmpty ?? false) { // if (wallet.seed?.isNotEmpty ?? false) {
// let restore from seed in this case; // let restore from seed in this case;
// final seed = wallet.seed; // final seed = wallet.seed;
// final credentials = MoneroRestoreWalletFromSeedCredentials( // final credentials = MoneroRestoreWalletFromSeedCredentials(
// name: name, password: password, mnemonic: seed, height: 2000000) // name: name, password: password, mnemonic: seed, height: 2000000)
// ..walletInfo = walletInfo; // ..walletInfo = walletInfo;
// await remove(name); // await remove(name);
// return restoreFromSeed(credentials); // return restoreFromSeed(credentials);
// } // }
throw MoneroWalletLoadingException(); throw MoneroWalletLoadingException();
@ -187,4 +192,38 @@ class MoneroWalletService extends WalletService<
rethrow; rethrow;
} }
} }
Future<void> repairOldAndroidWallet(String name) async {
try {
if (!Platform.isAndroid) {
return;
}
final oldAndroidWalletDirPath =
await outdatedAndroidPathForWalletDir(name: name);
final dir = Directory(oldAndroidWalletDirPath);
if (!dir.existsSync()) {
throw MoneroWalletLoadingException();
}
final newWalletDirPath =
await pathForWalletDir(name: name, type: WalletType.monero);
dir.listSync().forEach((f) {
final file = File(f.path);
final name = f.path.split('/').last;
final newPath = newWalletDirPath + '/$name';
final newFile = File(newPath);
print(file.path);
if (!newFile.existsSync()) {
newFile.createSync();
}
newFile.writeAsBytesSync(file.readAsBytesSync());
});
} catch (e) {
print(e.toString());
throw MoneroWalletLoadingException();
}
}
} }

View file

@ -5,6 +5,14 @@ import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/core/amount_converter.dart'; import 'package:cake_wallet/core/amount_converter.dart';
import 'package:cake_wallet/core/pending_transaction.dart'; import 'package:cake_wallet/core/pending_transaction.dart';
class DoubleSpendException implements Exception {
DoubleSpendException();
@override
String toString() =>
'This transaction cannot be committed. This can be due to many reasons including the wallet not being synced, there is not enough XMR in your available balance, or previous transactions are not yet fully processed.';
}
class PendingMoneroTransaction with PendingTransaction { class PendingMoneroTransaction with PendingTransaction {
PendingMoneroTransaction(this.pendingTransactionDescription); PendingMoneroTransaction(this.pendingTransactionDescription);
@ -22,7 +30,18 @@ class PendingMoneroTransaction with PendingTransaction {
CryptoCurrency.xmr, pendingTransactionDescription.fee); CryptoCurrency.xmr, pendingTransactionDescription.fee);
@override @override
Future<void> commit() async => Future<void> commit() async {
try {
monero_transaction_history.commitTransactionFromPointerAddress( monero_transaction_history.commitTransactionFromPointerAddress(
address: pendingTransactionDescription.pointerAddress); address: pendingTransactionDescription.pointerAddress);
} catch (e) {
final message = e.toString();
if (message.contains('Reason: double spend')) {
throw DoubleSpendException();
}
rethrow;
}
}
} }

View file

@ -40,13 +40,14 @@ class AuthPageState extends State<AuthPage> {
reaction((_) => widget.authViewModel.state, (ExecutionState state) { reaction((_) => widget.authViewModel.state, (ExecutionState state) {
if (state is ExecutedSuccessfullyState) { if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_authBar?.dismiss();
if (widget.onAuthenticationFinished != null) { if (widget.onAuthenticationFinished != null) {
widget.onAuthenticationFinished(true, this); widget.onAuthenticationFinished(true, this);
} else { } else {
_authBar?.dismiss();
showBar<void>(context, S.of(context).authenticated); showBar<void>(context, S.of(context).authenticated);
} }
}); });
setState(() {});
} }
if (state is IsExecutingState) { if (state is IsExecutingState) {

View file

@ -15,9 +15,10 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_h
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
class ReceivePage extends BasePage { class ReceivePage extends BasePage {
ReceivePage({this.addressListViewModel}); ReceivePage({this.addressListViewModel}) : _cryptoAmountFocus = FocusNode();
final WalletAddressListViewModel addressListViewModel; final WalletAddressListViewModel addressListViewModel;
@ -33,6 +34,8 @@ class ReceivePage extends BasePage {
@override @override
Color get titleColor => Colors.white; Color get titleColor => Colors.white;
final FocusNode _cryptoAmountFocus;
@override @override
Widget Function(BuildContext, Widget) get rootWrapper => Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => Container( (BuildContext context, Widget scaffold) => Container(
@ -67,53 +70,68 @@ class ReceivePage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
return SingleChildScrollView( return KeyboardActions(
child: Column( config: KeyboardActionsConfig(
children: <Widget>[ keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
Padding( keyboardBarColor: isDarkTheme
padding: EdgeInsets.fromLTRB(24, 80, 24, 40), ? Color.fromRGBO(48, 51, 60, 1.0)
child: QRWidget( : Color.fromRGBO(98, 98, 98, 1.0),
addressListViewModel: addressListViewModel, nextFocus: false,
isAmountFieldShow: true, actions: [
), KeyboardActionsItem(
), focusNode: _cryptoAmountFocus,
Observer( toolbarButtons: [(_) => KeyboardDoneButton()],
builder: (_) => ListView.separated( )
padding: EdgeInsets.all(0), ]),
separatorBuilder: (context, _) => Container( child: SingleChildScrollView(
height: 1, color: Theme.of(context).dividerColor), child: Column(
shrinkWrap: true, children: <Widget>[
physics: NeverScrollableScrollPhysics(), Padding(
itemCount: addressListViewModel.items.length, padding: EdgeInsets.fromLTRB(24, 80, 24, 40),
itemBuilder: (context, index) { child: QRWidget(
final item = addressListViewModel.items[index]; addressListViewModel: addressListViewModel,
Widget cell = Container(); isAmountFieldShow: true,
amountTextFieldFocusNode: _cryptoAmountFocus),
),
Observer(
builder: (_) => ListView.separated(
padding: EdgeInsets.all(0),
separatorBuilder: (context, _) => Container(
height: 1, color: Theme.of(context).dividerColor),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: addressListViewModel.items.length,
itemBuilder: (context, index) {
final item = addressListViewModel.items[index];
Widget cell = Container();
if (item is WalletAccountListHeader) { if (item is WalletAccountListHeader) {
cell = HeaderTile( cell = HeaderTile(
onTap: () async => await showPopUp<void>( onTap: () async => await showPopUp<void>(
context: context, context: context,
builder: (_) => builder: (_) =>
getIt.get<MoneroAccountListPage>()), getIt.get<MoneroAccountListPage>()),
title: S.of(context).accounts, title: S.of(context).accounts,
icon: Icon( icon: Icon(
Icons.arrow_forward_ios, Icons.arrow_forward_ios,
size: 14, size: 14,
color: Theme.of(context).textTheme.display1.color, color:
)); Theme.of(context).textTheme.display1.color,
} ));
}
if (item is WalletAddressListHeader) { if (item is WalletAddressListHeader) {
cell = HeaderTile( cell = HeaderTile(
onTap: () => Navigator.of(context) onTap: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress), .pushNamed(Routes.newSubaddress),
title: S.of(context).addresses, title: S.of(context).addresses,
icon: Icon( icon: Icon(
Icons.add, Icons.add,
size: 20, size: 20,
color: Theme.of(context).textTheme.display1.color, color:
)); Theme.of(context).textTheme.display1.color,
} ));
}
if (item is WalletAddressListItem) { if (item is WalletAddressListItem) {
final isFirst = addressListViewModel.isFirstAddress; final isFirst = addressListViewModel.isFirstAddress;
@ -146,17 +164,17 @@ class ReceivePage extends BasePage {
}); });
} }
return index != 0 return index != 0
? cell ? cell
: ClipRRect( : ClipRRect(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(30), topLeft: Radius.circular(30),
topRight: Radius.circular(30)), topRight: Radius.circular(30)),
child: cell, child: cell,
); );
})), })),
], ],
), ),
); ));
} }
} }

View file

@ -11,7 +11,9 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_v
class QRWidget extends StatelessWidget { class QRWidget extends StatelessWidget {
QRWidget( QRWidget(
{@required this.addressListViewModel, this.isAmountFieldShow = false}) {@required this.addressListViewModel,
this.isAmountFieldShow = false,
this.amountTextFieldFocusNode})
: amountController = TextEditingController(), : amountController = TextEditingController(),
_formKey = GlobalKey<FormState>() { _formKey = GlobalKey<FormState>() {
amountController.addListener(() => addressListViewModel.amount = amountController.addListener(() => addressListViewModel.amount =
@ -21,6 +23,7 @@ class QRWidget extends StatelessWidget {
final WalletAddressListViewModel addressListViewModel; final WalletAddressListViewModel addressListViewModel;
final bool isAmountFieldShow; final bool isAmountFieldShow;
final TextEditingController amountController; final TextEditingController amountController;
final FocusNode amountTextFieldFocusNode;
final GlobalKey<FormState> _formKey; final GlobalKey<FormState> _formKey;
@override @override
@ -45,7 +48,7 @@ class QRWidget extends StatelessWidget {
data: addressListViewModel.uri.toString(), data: addressListViewModel.uri.toString(),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Colors.white, foregroundColor: Colors.white,
//Theme.of(context).textTheme.headline.color, //Theme.of(context).textTheme.headline.color,
))))), ))))),
Spacer(flex: 3) Spacer(flex: 3)
]), ]),
@ -68,6 +71,7 @@ class QRWidget extends StatelessWidget {
child: Form( child: Form(
key: _formKey, key: _formKey,
child: BaseTextFormField( child: BaseTextFormField(
focusNode: amountTextFieldFocusNode,
controller: amountController, controller: amountController,
keyboardType: TextInputType.numberWithOptions( keyboardType: TextInputType.numberWithOptions(
decimal: true), decimal: true),

View file

@ -77,7 +77,7 @@ class SeedWidgetState extends State<SeedWidget> {
fontSize: 16.0, color: Theme.of(context).hintColor))), fontSize: 16.0, color: Theme.of(context).hintColor))),
Padding( Padding(
padding: EdgeInsets.only(right: 40, top: 10), padding: EdgeInsets.only(right: 40, top: 10),
child: ValidableAnnotatedEditableText( child: ValidatableAnnotatedEditableText(
cursorColor: Colors.blue, cursorColor: Colors.blue,
backgroundCursorColor: Colors.blue, backgroundCursorColor: Colors.blue,
validStyle: TextStyle( validStyle: TextStyle(

View file

@ -22,8 +22,8 @@ class TextAnnotation extends Comparable<TextAnnotation> {
int compareTo(TextAnnotation other) => text.compareTo(other.text); int compareTo(TextAnnotation other) => text.compareTo(other.text);
} }
class ValidableAnnotatedEditableText extends EditableText { class ValidatableAnnotatedEditableText extends EditableText {
ValidableAnnotatedEditableText({ ValidatableAnnotatedEditableText({
Key key, Key key,
FocusNode focusNode, FocusNode focusNode,
TextEditingController controller, TextEditingController controller,
@ -49,7 +49,7 @@ class ValidableAnnotatedEditableText extends EditableText {
controller: controller, controller: controller,
cursorColor: cursorColor, cursorColor: cursorColor,
style: validStyle, style: validStyle,
keyboardType: TextInputType.text, keyboardType: TextInputType.visiblePassword,
autocorrect: false, autocorrect: false,
autofocus: false, autofocus: false,
selectionColor: selectionColor, selectionColor: selectionColor,
@ -73,14 +73,14 @@ class ValidableAnnotatedEditableText extends EditableText {
final TextStyle invalidStyle; final TextStyle invalidStyle;
@override @override
ValidableAnnotatedEditableTextState createState() => ValidatableAnnotatedEditableTextState createState() =>
ValidableAnnotatedEditableTextState(); ValidatableAnnotatedEditableTextState();
} }
class ValidableAnnotatedEditableTextState extends EditableTextState { class ValidatableAnnotatedEditableTextState extends EditableTextState {
@override @override
ValidableAnnotatedEditableText get widget => ValidatableAnnotatedEditableText get widget =>
super.widget as ValidableAnnotatedEditableText; super.widget as ValidatableAnnotatedEditableText;
List<Annotation> getRanges() { List<Annotation> getRanges() {
final result = List<Annotation>(); final result = List<Annotation>();

View file

@ -110,6 +110,8 @@ abstract class AuthViewModelBase with Store {
if (isAuthenticated) { if (isAuthenticated) {
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
} else {
state = FailureState('Failure biometric authentication');
} }
} }
} catch(e) { } catch(e) {

View file

@ -401,10 +401,10 @@ packages:
description: description:
path: "." path: "."
ref: cake ref: cake
resolved-ref: a734c2ea3239f9153dba6f5bec740e1df54ee754 resolved-ref: d4d68a9c1e4c45eb236cd7a5a2fac84c394a7605
url: "https://github.com/cake-tech/flutter_secure_storage.git" url: "https://github.com/cake-tech/flutter_secure_storage.git"
source: git source: git
version: "3.3.55" version: "3.3.57"
flutter_slidable: flutter_slidable:
dependency: "direct main" dependency: "direct main"
description: description:
@ -498,7 +498,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.18" version: "2.1.19"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1000,7 +1000,7 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.3" version: "1.7.4"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View file

@ -11,7 +11,7 @@ description: Cake Wallet.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 4.0.4+14 version: 4.0.8+20
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"
@ -31,6 +31,7 @@ dependencies:
git: git:
url: https://github.com/cake-tech/flutter_secure_storage.git url: https://github.com/cake-tech/flutter_secure_storage.git
ref: cake ref: cake
version: 3.3.57
provider: ^3.1.0 provider: ^3.1.0
rxdart: ^0.22.2 rxdart: ^0.22.2
yaml: ^2.1.16 yaml: ^2.1.16