Merge branch '4.1.0' into CAKE-222-fix-fee-picker-on-the-settings-screen

This commit is contained in:
OleksandrSobol 2021-01-08 17:38:57 +02:00
commit 0e721b7795
47 changed files with 1402 additions and 642 deletions

View file

@ -354,7 +354,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 12;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -494,7 +494,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 12;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -528,7 +528,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 12;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:intl/intl.dart';
import 'package:cake_wallet/entities/crypto_amount_format.dart';
@ -7,10 +9,32 @@ final bitcoinAmountFormat = NumberFormat()
..maximumFractionDigits = bitcoinAmountLength
..minimumFractionDigits = 1;
String bitcoinAmountToString({int amount}) =>
bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format(
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
double bitcoinAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
double bitcoinAmountToDouble({int amount}) =>
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
int doubleToBitcoinAmount(double amount) =>
(amount * bitcoinAmountDivider).toInt();
int stringDoubleToBitcoinAmount(String amount) {
final splitted = amount.split('');
final dotIndex = amount.indexOf('.');
int result = 0;
for (var i = 0; i < splitted.length; i++) {
try {
if (dotIndex == i) {
continue;
}
final char = splitted[i];
final multiplier = dotIndex < i
? bitcoinAmountDivider ~/ pow(10, (i - dotIndex))
: (bitcoinAmountDivider * pow(10, (dotIndex - i -1))).toInt();
final num = int.parse(char) * multiplier;
result += num;
} catch (_) {}
}
return result;
}

View file

@ -27,10 +27,10 @@ class BitcoinBalance extends Balance {
final int confirmed;
final int unconfirmed;
int get total =>
confirmed + (unconfirmed < 0 ? unconfirmed * -1 : unconfirmed);
int get total => confirmed + unconfirmed;
int get availableBalance => confirmed + (unconfirmed < 0 ? unconfirmed : 0);
int get availableBalance =>
(confirmed ?? 0) + (unconfirmed < 0 ? unconfirmed : 0);
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);

View file

@ -4,6 +4,6 @@ class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(this.address, this.amount, this.priority);
final String address;
final double amount;
final String amount;
TransactionPriority priority;
}

View file

@ -47,7 +47,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final out = vin['tx']['vout'][vout] as Map;
final outAddresses =
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
inputsAmount += doubleToBitcoinAmount(out['value'] as double ?? 0);
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
direction = TransactionDirection.outgoing;
@ -58,7 +58,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? [];
final ntrs = outAddresses.toSet().intersection(addressesSet);
final value = doubleToBitcoinAmount(out['value'] as double ?? 0.0);
final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString());
totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||

View file

@ -116,6 +116,19 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
walletInfo: walletInfo);
}
static int feeAmountForPriority(TransactionPriority priority) {
switch (priority) {
case TransactionPriority.slow:
return 6000;
case TransactionPriority.regular:
return 22080;
case TransactionPriority.fast:
return 24000;
default:
return 0;
}
}
@override
final BitcoinTransactionHistory transactionHistory;
final String path;
@ -243,16 +256,20 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Object credentials) async {
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final fee = _feeMultiplier(transactionCredentials.priority);
final fee = feeAmountForPriority(transactionCredentials.priority);
final amount = transactionCredentials.amount != null
? doubleToBitcoinAmount(transactionCredentials.amount)
: balance.total - fee;
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: balance.availableBalance - fee;
final totalAmount = amount + fee;
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
var leftAmount = totalAmount;
final changeAddress = address;
var leftAmount = totalAmount;
var totalInputAmount = 0;
if (totalAmount > balance.availableBalance) {
throw BitcoinTransactionWrongBalanceException();
}
final unspent = addresses.map((address) => eclient
.getListUnspentWithAddress(address.address)
.then((unspent) => unspent
@ -334,7 +351,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
@override
double calculateEstimatedFee(TransactionPriority priority) =>
bitcoinAmountToDouble(amount: _feeMultiplier(priority));
bitcoinAmountToDouble(amount: feeAmountForPriority(priority));
@override
Future<void> save() async {
@ -386,17 +403,4 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
String _getAddress({@required int index}) =>
generateAddress(hd: hd, index: index);
int _feeMultiplier(TransactionPriority priority) {
switch (priority) {
case TransactionPriority.slow:
return 6000;
case TransactionPriority.regular:
return 22080;
case TransactionPriority.fast:
return 24000;
default:
return 0;
}
}
}

View file

@ -22,9 +22,8 @@ String jsonrpcparams(List<Object> params) {
}
String jsonrpc(
{String method, List<Object> params, int id, double version = 2.0}) =>
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json
.encode(params)}}\n';
{String method, List<Object> params, int id, double version = 2.0}) =>
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
class SocketTask {
SocketTask({this.completer, this.isSubscription, this.subject});
@ -75,7 +74,9 @@ class ElectrumClient {
socket.listen((Uint8List event) {
try {
_handleResponse(utf8.decode(event.toList()));
final response =
json.decode(utf8.decode(event.toList())) as Map<String, Object>;
_handleResponse(response);
} on FormatException catch (e) {
final msg = e.message.toLowerCase();
@ -87,12 +88,33 @@ class ElectrumClient {
unterminatedString += e.source as String;
}
if (msg.contains("not a subtype of type")) {
unterminatedString += e.source as String;
return;
}
if (isJSONStringCorrect(unterminatedString)) {
_handleResponse(unterminatedString);
final response =
json.decode(unterminatedString) as Map<String, Object>;
_handleResponse(response);
unterminatedString = null;
}
} on TypeError catch (e) {
if (!e.toString().contains('Map<String, Object>')) {
return;
}
final source = utf8.decode(event.toList());
unterminatedString += source;
if (isJSONStringCorrect(unterminatedString)) {
final response =
json.decode(unterminatedString) as Map<String, Object>;
_handleResponse(response);
unterminatedString = null;
}
} catch (e) {
print(e);
print(e.toString());
}
}, onError: (Object error) {
print(error.toString());
@ -153,7 +175,7 @@ class ElectrumClient {
});
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address) =>
String address) =>
call(
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address)]).then((dynamic result) {
@ -204,7 +226,7 @@ class ElectrumClient {
});
Future<Map<String, Object>> getTransactionRaw(
{@required String hash}) async =>
{@required String hash}) async =>
call(method: 'blockchain.transaction.get', params: [hash, true])
.then((dynamic result) {
if (result is Map<String, Object>) {
@ -233,25 +255,25 @@ class ElectrumClient {
}
Future<String> broadcastTransaction(
{@required String transactionRaw}) async =>
{@required String transactionRaw}) async =>
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
.then((dynamic result) {
if (result is String) {
return result;
}
print(result);
return '';
});
Future<Map<String, dynamic>> getMerkle(
{@required String hash, @required int height}) async =>
{@required String hash, @required int height}) async =>
await call(
method: 'blockchain.transaction.get_merkle',
params: [hash, height]) as Map<String, dynamic>;
Future<Map<String, dynamic>> getHeader({@required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height])
as Map<String, dynamic>;
as Map<String, dynamic>;
Future<double> estimatefee({@required int p}) =>
call(method: 'blockchain.estimatefee', params: [p])
@ -275,9 +297,10 @@ class ElectrumClient {
params: [scripthash]);
}
BehaviorSubject<T> subscribe<T>({@required String id,
@required String method,
List<Object> params = const []}) {
BehaviorSubject<T> subscribe<T>(
{@required String id,
@required String method,
List<Object> params = const []}) {
final subscription = BehaviorSubject<T>();
_regisrySubscription(id, subscription);
socket.write(jsonrpc(method: method, id: _id, params: params));
@ -296,9 +319,10 @@ class ElectrumClient {
return completer.future;
}
Future<dynamic> callWithTimeout({String method,
List<Object> params = const [],
int timeout = 2000}) async {
Future<dynamic> callWithTimeout(
{String method,
List<Object> params = const [],
int timeout = 2000}) async {
final completer = Completer<dynamic>();
_id += 1;
final id = _id;
@ -325,9 +349,8 @@ class ElectrumClient {
onConnectionStatusChange = null;
}
void _regisryTask(int id, Completer completer) =>
_tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false);
void _regisryTask(int id, Completer completer) => _tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false);
void _regisrySubscription(String id, BehaviorSubject subject) =>
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
@ -371,22 +394,20 @@ class ElectrumClient {
_isConnected = isConnected;
}
void _handleResponse(String response) {
print('Response: $response');
final jsoned = json.decode(response) as Map<String, Object>;
// print(jsoned);
final method = jsoned['method'];
final id = jsoned['id'] as String;
final result = jsoned['result'];
void _handleResponse(Map<String, Object> response) {
final method = response['method'];
final id = response['id'] as String;
final result = response['result'];
if (method is String) {
_methodHandler(method: method, request: jsoned);
_methodHandler(method: method, request: response);
return;
}
_finish(id, result);
}
}
// FIXME: move me
bool isJSONStringCorrect(String source) {
try {

View file

@ -28,6 +28,7 @@ import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/settings/change_language.dart';
import 'package:cake_wallet/src/screens/settings/settings.dart';
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
@ -55,6 +56,8 @@ import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/rescan_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/auth_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@ -355,7 +358,8 @@ Future setup(
getIt.get<AppStore>().wallet,
tradesSource,
getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>()));
getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet,
@ -415,11 +419,17 @@ Future setup(
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>
((TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
transactionInfo: transactionInfo,
transactionDescriptionBox: transactionDescriptionBox,
settingsStore: getIt.get<SettingsStore>()
));
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionInfo,
getIt.get<SettingsStore>().shouldSaveRecipientAddress,
transactionDescriptionBox));
transactionDetailsViewModel: getIt
.get<TransactionDetailsViewModel>(param1: transactionInfo)));
getIt.registerFactoryParam<NewWalletTypePage,
void Function(BuildContext, WalletType), bool>(
@ -428,4 +438,10 @@ Future setup(
getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
(WalletType type, _) => PreSeedPage(type));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel(tradeForDetails: trade, trades: tradesSource));
getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
}

View file

@ -4,7 +4,7 @@ part 'transaction_description.g.dart';
@HiveType(typeId: 2)
class TransactionDescription extends HiveObject {
TransactionDescription({this.id, this.recipientAddress});
TransactionDescription({this.id, this.recipientAddress, this.transactionNote});
static const boxName = 'TransactionDescriptions';
static const boxKey = 'transactionDescriptionsBoxKey';
@ -14,4 +14,9 @@ class TransactionDescription extends HiveObject {
@HiveField(1)
String recipientAddress;
@HiveField(2)
String transactionNote;
String get note => transactionNote ?? '';
}

File diff suppressed because it is too large Load diff

View file

@ -279,7 +279,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.tradeDetails:
return MaterialPageRoute<void>(
builder: (_) => TradeDetailsPage(settings.arguments as Trade));
builder: (_) =>
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.restoreWalletFromSeedDetails:
final args = settings.arguments as List;

View file

@ -62,10 +62,11 @@ class ExchangePage extends BasePage {
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).reset, onPressed: () {
caption: S.of(context).reset,
onPressed: () {
_formKey.currentState.reset();
exchangeViewModel.reset();
});
});
@override
Widget body(BuildContext context) {
@ -95,8 +96,8 @@ class ExchangePage extends BasePage {
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2
.backgroundColor,
keyboardBarColor:
Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
@ -160,6 +161,11 @@ class ExchangePage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount
? () => exchangeViewModel
.calculateDepositAllAmount()
: null,
amountFocusNode: _depositAmountFocus,
key: depositKey,
title: S.of(context).you_will_send,
@ -394,30 +400,35 @@ class ExchangePage extends BasePage {
}),
),
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).exchange,
onPressed: () {
if (_formKey.currentState.validate()) {
if ((exchangeViewModel.depositCurrency == CryptoCurrency.xmr)
&&(!(exchangeViewModel.status is SyncedSyncStatus))) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).exchange,
alertContent: S.of(context).exchange_sync_alert_content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
} else {
exchangeViewModel.createTrade();
}
}
},
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isLoading: exchangeViewModel.tradeState
is TradeIsCreating)),
builder: (_) => LoadingPrimaryButton(
text: S.of(context).exchange,
onPressed: () {
if (_formKey.currentState.validate()) {
if ((exchangeViewModel.depositCurrency ==
CryptoCurrency.xmr) &&
(!(exchangeViewModel.status
is SyncedSyncStatus))) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).exchange,
alertContent: S
.of(context)
.exchange_sync_alert_content,
buttonText: S.of(context).ok,
buttonAction: () =>
Navigator.of(context).pop());
});
} else {
exchangeViewModel.createTrade();
}
}
},
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isLoading:
exchangeViewModel.tradeState is TradeIsCreating)),
]),
)),
));

View file

@ -27,7 +27,9 @@ class ExchangeCard extends StatefulWidget {
this.borderColor = Colors.transparent,
this.currencyValueValidator,
this.addressTextFieldValidator,
this.amountFocusNode})
this.amountFocusNode,
this.hasAllAmount = false,
this.allAmount})
: super(key: key);
final List<CryptoCurrency> currencies;
@ -47,6 +49,8 @@ class ExchangeCard extends StatefulWidget {
final FormFieldValidator<String> currencyValueValidator;
final FormFieldValidator<String> addressTextFieldValidator;
final FocusNode amountFocusNode;
final bool hasAllAmount;
Function allAmount;
@override
ExchangeCardState createState() => ExchangeCardState();
@ -197,7 +201,36 @@ class ExchangeCardState extends State<ExchangeCard> {
]),
),
),
)
),
if (widget.hasAllAmount)
Positioned(
top: 5,
right: 55,
child: Container(
height: 32,
width: 32,
margin: EdgeInsets.only(left: 14, top: 4, bottom: 10),
decoration: BoxDecoration(
color: Theme.of(context)
.primaryTextTheme
.display1
.color,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: InkWell(
onTap: () => widget.allAmount?.call(),
child: Center(
child: Text(S.of(context).all,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.display1
.decorationColor)),
),
),
))
],
)),
Padding(
@ -232,18 +265,17 @@ class ExchangeCardState extends State<ExchangeCard> {
),
!_isAddressEditable && widget.hasRefundAddress
? Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
S.of(context).refund_address,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color:
Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
))
padding: EdgeInsets.only(top: 20),
child: Text(
S.of(context).refund_address,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
))
: Offstage(),
_isAddressEditable
? Padding(
@ -251,7 +283,8 @@ class ExchangeCardState extends State<ExchangeCard> {
child: AddressTextField(
controller: addressController,
placeholder: widget.hasRefundAddress
? S.of(context).refund_address : null,
? S.of(context).refund_address
: null,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
@ -265,8 +298,7 @@ class ExchangeCardState extends State<ExchangeCard> {
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color:
Theme.of(context)
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
@ -281,8 +313,8 @@ class ExchangeCardState extends State<ExchangeCard> {
onTap: () {
Clipboard.setData(
ClipboardData(text: addressController.text));
showBar<void>(context,
S.of(context).copied_to_clipboard);
showBar<void>(
context, S.of(context).copied_to_clipboard);
},
child: Row(
mainAxisSize: MainAxisSize.max,

View file

@ -6,8 +6,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
@ -172,36 +170,14 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
),
itemBuilder: (context, index) {
final item = widget.exchangeTradeViewModel.items[index];
String value;
final value = item.data ?? fetchingLabel;
final content = Observer(builder: (_) {
switch (index) {
case 0:
value =
'${widget.exchangeTradeViewModel.trade.id ?? fetchingLabel}';
break;
case 1:
value =
'${widget.exchangeTradeViewModel.trade.amount ?? fetchingLabel}';
break;
case 2:
value =
'${widget.exchangeTradeViewModel.trade.state ?? fetchingLabel}';
break;
case 3:
value = widget.exchangeTradeViewModel.trade
.inputAddress ??
fetchingLabel;
break;
}
return StandartListRow(
title: item.title,
value: value,
valueFontSize: 14,
image: item.isCopied ? copyImage : null,
);
});
final content = StandartListRow(
title: item.title,
value: value,
valueFontSize: 14,
image: item.isCopied ? copyImage : null,
);
return item.isCopied
? Builder(
@ -382,7 +358,16 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
});
});
},
actionLeftButton: () => Navigator.of(context).pop());
actionLeftButton: () => Navigator.of(context).pop(),
feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel
.pendingTransaction.feeFormatted,
fiatAmountValue: widget.exchangeTradeViewModel.sendViewModel
.pendingTransactionFeeFiatAmount +
' ' +
widget.exchangeTradeViewModel.sendViewModel.fiat.title,
recipientTitle: S.of(context).recipient_address,
recipientAddress:
widget.exchangeTradeViewModel.sendViewModel.address);
});
});
}

View file

@ -49,7 +49,7 @@ class PreSeedPage extends BasePage {
Padding(
padding: EdgeInsets.only(top: 70, left: 16, right: 16),
child: Text(
S.of(context).pre_seed_description(wordsCount),
S.of(context).pre_seed_description(wordsCount.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,

View file

@ -31,6 +31,7 @@ class SendPage extends BasePage {
: _addressController = TextEditingController(),
_cryptoAmountController = TextEditingController(),
_fiatAmountController = TextEditingController(),
_noteController = TextEditingController(),
_formKey = GlobalKey<FormState>(),
_cryptoAmountFocus = FocusNode(),
_fiatAmountFocus = FocusNode(),
@ -46,6 +47,7 @@ class SendPage extends BasePage {
final TextEditingController _addressController;
final TextEditingController _cryptoAmountController;
final TextEditingController _fiatAmountController;
final TextEditingController _noteController;
final GlobalKey<FormState> _formKey;
final FocusNode _cryptoAmountFocus;
final FocusNode _fiatAmountFocus;
@ -304,6 +306,30 @@ class SendPage extends BasePage {
fontWeight: FontWeight.w500,
fontSize: 14),
)),
Padding(
padding: EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: _noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
hintText: S.of(context).note_optional,
placeholderTextStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor),
),
),
Observer(
builder: (_) => GestureDetector(
onTap: () =>
@ -313,6 +339,7 @@ class SendPage extends BasePage {
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S
@ -326,23 +353,50 @@ class SendPage extends BasePage {
color: Colors.white)),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
sendViewModel
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
sendViewModel
.estimatedFee
.toString() +
' ' +
sendViewModel
' ' +
sendViewModel
.currency.title,
style: TextStyle(
fontSize: 12,
fontWeight:
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
//color: Theme.of(context).primaryTextTheme.display2.color,
color:
//color: Theme.of(context).primaryTextTheme.display2.color,
color:
Colors.white)),
Padding(
padding:
EdgeInsets.only(top: 5),
child: Text(
sendViewModel
.estimatedFeeFiatAmount
+ ' ' +
sendViewModel
.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
color: Theme
.of(context)
.primaryTextTheme
.headline
.decorationColor))
),
],
),
Padding(
padding: EdgeInsets.only(
top: 2,
left: 5),
child: Icon(
Icons.arrow_forward_ios,
@ -534,6 +588,14 @@ class SendPage extends BasePage {
}
});
_noteController.addListener(() {
final note = _noteController.text ?? '';
if (note != sendViewModel.note) {
sendViewModel.note = note;
}
});
reaction((_) => sendViewModel.sendAll, (bool all) {
if (all) {
_cryptoAmountController.text = S.current.all;
@ -571,6 +633,12 @@ class SendPage extends BasePage {
}
});
reaction((_) => sendViewModel.note, (String note) {
if (note != _noteController.text) {
_noteController.text = note;
}
});
reaction((_) => sendViewModel.state, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -596,8 +664,14 @@ class SendPage extends BasePage {
amount: S.of(context).send_amount,
amountValue:
sendViewModel.pendingTransaction.amountFormatted,
fiatAmountValue: sendViewModel.pendingTransactionFiatAmount
+ ' ' + sendViewModel.fiat.title,
fee: S.of(context).send_fee,
feeValue: sendViewModel.pendingTransaction.feeFormatted,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmount
+ ' ' + sendViewModel.fiat.title,
recipientTitle: S.of(context).recipient_address,
recipientAddress: sendViewModel.address,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
@ -614,96 +688,17 @@ class SendPage extends BasePage {
}
if (state is TransactionCommitted) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
Center(
child: Padding(
padding: EdgeInsets.only(
top: 220, left: 24, right: 24),
child: Text(
S.of(context).send_success(
sendViewModel.currency
.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
),
),
),
),
Positioned(
left: 24,
right: 24,
bottom: 24,
child: PrimaryButton(
onPressed: () =>
Navigator.of(context).pop(),
text: S.of(context).send_got_it,
color: Theme.of(context)
.accentTextTheme
.body2
.color,
textColor: Colors.white))
],
);
return AlertWithOneAction(
alertTitle: '',
alertContent: S.of(context).send_success(
sendViewModel.currency
.toString()),
buttonText: S.of(context).ok,
buttonAction: () =>
Navigator.of(context).pop());
}
if (state is TransactionCommitting) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 3.0, sigmaY: 3.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.backgroundColor
.withOpacity(0.25)),
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 220),
child: Text(
S.of(context).send_sending,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
),
),
),
),
),
)
],
);
}
return Container();
return Offstage();
});
});
},

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
@ -6,8 +7,12 @@ class ConfirmSendingAlert extends BaseAlertDialog {
@required this.alertTitle,
@required this.amount,
@required this.amountValue,
@required this.fiatAmountValue,
@required this.fee,
@required this.feeValue,
@required this.feeFiatAmount,
@required this.recipientTitle,
@required this.recipientAddress,
@required this.leftButtonText,
@required this.rightButtonText,
@required this.actionLeftButton,
@ -18,8 +23,12 @@ class ConfirmSendingAlert extends BaseAlertDialog {
final String alertTitle;
final String amount;
final String amountValue;
final String fiatAmountValue;
final String fee;
final String feeValue;
final String feeFiatAmount;
final String recipientTitle;
final String recipientAddress;
final String leftButtonText;
final String rightButtonText;
final VoidCallback actionLeftButton;
@ -29,74 +38,145 @@ class ConfirmSendingAlert extends BaseAlertDialog {
@override
String get titleText => alertTitle;
@override
bool get isDividerExists => true;
@override
String get leftActionButtonText => leftButtonText;
@override
String get rightActionButtonText => rightButtonText;
@override
VoidCallback get actionLeft => actionLeftButton;
@override
VoidCallback get actionRight => actionRightButton;
@override
bool get barrierDismissible => alertBarrierDismissible;
@override
Widget content(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
amount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
Text(
amountValue,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
amountValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
Text(
fiatAmountValue,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
)
],
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
fee,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
Padding(
padding: EdgeInsets.only(top: 16),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
fee,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
),
Text(
feeValue,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
feeValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
Text(
feeFiatAmount,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
)
],
)
),
Padding(
padding: EdgeInsets.fromLTRB(8, 16, 8, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
recipientTitle,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
)
],
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
recipientAddress,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
)
],
),
)
],
);

View file

@ -1,82 +1,43 @@
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
class TradeDetailsPage extends BasePage {
TradeDetailsPage(this.trade) : _items = [] {
final dateFormat = DateFormatter.withCurrentLocal();
_items.addAll([
StandartListItem(title: S.current.trade_details_id, value: trade.id),
StandartListItem(
title: S.current.trade_details_state,
value: trade.state != null
? trade.state.toString()
: S.current.trade_details_fetching)
]);
if (trade.provider != null) {
_items.add(StandartListItem(
title: S.current.trade_details_provider,
value: trade.provider.toString()));
}
if (trade.createdAt != null) {
_items.add(StandartListItem(
title: S.current.trade_details_created_at,
value: dateFormat.format(trade.createdAt).toString()));
}
if (trade.from != null && trade.to != null) {
_items.add(StandartListItem(
title: S.current.trade_details_pair,
value: '${trade.from.toString()}${trade.to.toString()}'));
}
}
TradeDetailsPage(this.tradeDetailsViewModel);
@override
String get title => S.current.trade_details_title;
final Trade trade;
final List<StandartListItem> _items;
final TradeDetailsViewModel tradeDetailsViewModel;
@override
Widget body(BuildContext context) {
return Container(child: Observer(builder: (_) {
return ListView.separated(
separatorBuilder: (_, __) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color: Theme.of(context)
.primaryTextTheme
.title
.backgroundColor)),
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) {
final item = _items[index];
final isDrawBottom = index == _items.length - 1 ? true : false;
return Observer(builder: (_) {
return SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => tradeDetailsViewModel.items.length,
itemBuilder: (_, __, index) {
final item = tradeDetailsViewModel.items[index];
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: '${item.value}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
showBar<void>(context, S
.of(context)
.copied_to_clipboard);
},
child: StandartListRow(
title: '${item.title}',
value: '${item.value}',
isDrawBottom: isDrawBottom,
title: '${item.title}',
value: '${item.value}'
));
});
}));
});
}
}

View file

@ -1,6 +1,6 @@
class StandartListItem {
StandartListItem({this.title, this.value});
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
final String title;
final String value;
class StandartListItem extends TransactionDetailsListItem {
StandartListItem({String title, String value})
: super(title: title, value: value);
}

View file

@ -0,0 +1,8 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
class TextFieldListItem extends TransactionDetailsListItem {
TextFieldListItem({String title, String value, this.onSubmitted})
: super(title: title, value: value);
final Function(String value) onSubmitted;
}

View file

@ -0,0 +1,6 @@
abstract class TransactionDetailsListItem {
TransactionDetailsListItem({this.title, this.value});
final String title;
final String value;
}

View file

@ -1,107 +1,32 @@
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/monero/monero_transaction_info.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:hive/hive.dart';
class TransactionDetailsPage extends BasePage {
TransactionDetailsPage(this.transactionInfo, bool showRecipientAddress,
Box<TransactionDescription> transactionDescriptionBox)
: _items = [] {
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
if (tx is MoneroTransactionInfo) {
final items = [
StandartListItem(
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date)),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
];
if (tx.key?.isNotEmpty ?? null) {
// FIXME: add translation
items.add(StandartListItem(title: 'Transaction Key', value: tx.key));
}
_items.addAll(items);
}
if (tx is BitcoinTransactionInfo) {
final items = [
StandartListItem(
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date)),
StandartListItem(
title: 'Confirmations', value: tx.confirmations?.toString()),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty)
StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
];
_items.addAll(items);
}
if (showRecipientAddress) {
final recipientAddress = transactionDescriptionBox.values
.firstWhere((val) => val.id == transactionInfo.id, orElse: () => null)
?.recipientAddress;
if (recipientAddress?.isNotEmpty ?? false) {
_items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: recipientAddress));
}
}
}
TransactionDetailsPage({this.transactionDetailsViewModel});
@override
String get title => S.current.transaction_details_title;
final TransactionInfo transactionInfo;
final List<StandartListItem> _items;
final TransactionDetailsViewModel transactionDetailsViewModel;
@override
Widget body(BuildContext context) {
return Container(
child: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color:
Theme.of(context).primaryTextTheme.title.backgroundColor,
),
),
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
final isDrawBottom = index == _items.length - 1 ? true : false;
return SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length,
itemBuilder: (_, __, index) {
final item = transactionDetailsViewModel.items[index];
if (item is StandartListItem) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
@ -110,10 +35,19 @@ class TransactionDetailsPage extends BasePage {
},
child: StandartListRow(
title: '${item.title}:',
value: item.value,
isDrawBottom: isDrawBottom),
value: item.value),
);
}),
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
return null;
});
}
}

View file

@ -0,0 +1,71 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
class TextFieldListRow extends StatelessWidget {
TextFieldListRow(
{this.title,
this.value,
this.titleFontSize = 14,
this.valueFontSize = 16,
this.onSubmitted}) {
_textController = TextEditingController();
_textController.text = value;
}
final String title;
final String value;
final double titleFontSize;
final double valueFontSize;
final Function(String value) onSubmitted;
TextEditingController _textController;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Theme.of(context).backgroundColor,
child: Padding(
padding:
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title,
style: TextStyle(
fontSize: titleFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme.overline.color),
textAlign: TextAlign.left),
TextField(
controller: _textController,
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.done,
maxLines: null,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme.title.color),
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.only(top: 12, bottom: 0),
hintText: S.of(context).enter_your_note,
hintStyle: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme.overline.color),
border: InputBorder.none
),
onSubmitted: (value) => onSubmitted.call(value),
)
]),
),
);
}
}

View file

@ -7,6 +7,7 @@ class BaseAlertDialog extends StatelessWidget {
String get contentText => '';
String get leftActionButtonText => '';
String get rightActionButtonText => '';
bool get isDividerExists => false;
VoidCallback get actionLeft => () {};
VoidCallback get actionRight => () {};
bool get barrierDismissible => true;
@ -127,18 +128,27 @@ class BaseAlertDialog extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(24, 32, 24, 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
title(context),
Padding(
padding: EdgeInsets.only(top: 8),
child: content(context),
)
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 32, 24, 0),
child: title(context),
),
isDividerExists
? Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
)
: Offstage(),
Padding(
padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
child: content(context),
)
],
),
Container(
height: 1,

View file

@ -51,7 +51,7 @@ class BaseTextFormField extends StatelessWidget {
final FocusNode focusNode;
final bool readOnly;
final bool enableInteractiveSelection;
String initialValue;
final String initialValue;
@override
Widget build(BuildContext context) {

View file

@ -7,77 +7,60 @@ class StandartListRow extends StatelessWidget {
this.value,
this.titleFontSize = 14,
this.valueFontSize = 16,
this.image,
this.isDrawBottom = false});
this.image});
final String title;
final String value;
final double titleFontSize;
final double valueFontSize;
final Image image;
final bool isDrawBottom;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
width: double.infinity,
color: Theme.of(context).backgroundColor,
child: Padding(
padding:
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title,
style: TextStyle(
fontSize: titleFontSize,
fontWeight: FontWeight.w500,
color:
Theme.of(context).primaryTextTheme.overline.color),
textAlign: TextAlign.left),
Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(value,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color)),
),
image != null
? Padding(
padding: EdgeInsets.only(left: 24),
child: image,
)
: Offstage()
],
return Container(
width: double.infinity,
color: Theme.of(context).backgroundColor,
child: Padding(
padding:
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title,
style: TextStyle(
fontSize: titleFontSize,
fontWeight: FontWeight.w500,
color:
Theme.of(context).primaryTextTheme.overline.color),
textAlign: TextAlign.left),
Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(value,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color)),
),
)
]),
),
),
isDrawBottom
? Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).primaryTextTheme.title.backgroundColor,
),
)
: Offstage(),
],
image != null
? Padding(
padding: EdgeInsets.only(left: 24),
child: image,
)
: Offstage()
],
),
)
]),
),
);
}
}

View file

@ -85,6 +85,11 @@ abstract class SettingsStoreBase with Store {
(String languageCode) => sharedPreferences.setString(
PreferencesKey.currentLanguageCode, languageCode));
reaction((_) => balanceDisplayMode,
(BalanceDisplayMode mode) => sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey,
mode.serialize()));
this
.nodes
.observe((change) => _saveCurrentNode(change.newValue, change.key));
@ -153,7 +158,7 @@ abstract class SettingsStoreBase with Store {
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
false;
final legacyTheme =
sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy)
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index
: ThemeType.bright.index;
final savedTheme = ThemeList.deserialize(

View file

@ -1,5 +1,9 @@
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/monero/monero_balance.dart';
import 'package:cake_wallet/monero/monero_wallet.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
@ -18,8 +22,30 @@ abstract class BalanceViewModelBase with Store {
BalanceViewModelBase(
{@required this.appStore,
@required this.settingsStore,
@required this.fiatConvertationStore})
: isReversing = false;
@required this.fiatConvertationStore}) {
isReversing = false;
wallet ??= appStore.wallet;
_reaction = reaction((_) => appStore.wallet, _onWalletChange);
final _wallet = wallet;
if (_wallet is MoneroWallet) {
balance = _wallet.balance;
}
if (_wallet is BitcoinWallet) {
balance = _wallet.balance;
}
_onCurrentWalletChangeReaction =
reaction<void>((_) => wallet.balance, (dynamic balance) {
if (balance is Balance) {
this.balance = balance;
}
});
}
final AppStore appStore;
final SettingsStore settingsStore;
@ -31,6 +57,12 @@ abstract class BalanceViewModelBase with Store {
@observable
bool isReversing;
@observable
Balance balance;
@observable
WalletBase wallet;
@computed
double get price => fiatConvertationStore.prices[appStore.wallet.currency];
@ -47,24 +79,24 @@ abstract class BalanceViewModelBase with Store {
@computed
String get cryptoBalance {
final walletBalance = _walletBalance;
var balance = '---';
var _balance = '---';
if (displayMode == BalanceDisplayMode.availableBalance) {
balance = walletBalance.unlockedBalance ?? '0.0';
_balance = walletBalance.unlockedBalance ?? '0.0';
}
if (displayMode == BalanceDisplayMode.fullBalance) {
balance = walletBalance.totalBalance ?? '0.0';
_balance = walletBalance.totalBalance ?? '0.0';
}
return balance;
return _balance;
}
@computed
String get fiatBalance {
final walletBalance = _walletBalance;
final fiatCurrency = settingsStore.fiatCurrency;
var balance = '---';
var _balance = '---';
final totalBalance =
_getFiatBalance(price: price, cryptoAmount: walletBalance.totalBalance);
@ -73,30 +105,30 @@ abstract class BalanceViewModelBase with Store {
price: price, cryptoAmount: walletBalance.unlockedBalance);
if (displayMode == BalanceDisplayMode.availableBalance) {
balance = fiatCurrency.toString() + ' ' + unlockedBalance ?? '0.00';
_balance = fiatCurrency.toString() + ' ' + unlockedBalance ?? '0.00';
}
if (displayMode == BalanceDisplayMode.fullBalance) {
balance = fiatCurrency.toString() + ' ' + totalBalance ?? '0.00';
_balance = fiatCurrency.toString() + ' ' + totalBalance ?? '0.00';
}
return balance;
return _balance;
}
@computed
WalletBalance get _walletBalance {
final _wallet = appStore.wallet;
final _balance = balance;
if (_wallet is MoneroWallet) {
if (_balance is MoneroBalance) {
return WalletBalance(
unlockedBalance: _wallet.balance.formattedUnlockedBalance,
totalBalance: _wallet.balance.formattedFullBalance);
unlockedBalance: _balance.formattedUnlockedBalance,
totalBalance: _balance.formattedFullBalance);
}
if (_wallet is BitcoinWallet) {
if (_balance is BitcoinBalance) {
return WalletBalance(
unlockedBalance: _wallet.balance.availableBalanceFormatted,
totalBalance: _wallet.balance.totalFormatted);
unlockedBalance: _balance.availableBalanceFormatted,
totalBalance: _balance.totalFormatted);
}
return null;
@ -105,6 +137,30 @@ abstract class BalanceViewModelBase with Store {
@computed
CryptoCurrency get currency => appStore.wallet.currency;
ReactionDisposer _onCurrentWalletChangeReaction;
ReactionDisposer _reaction;
@action
void _onWalletChange(WalletBase wallet) {
this.wallet = wallet;
if (wallet is MoneroWallet) {
balance = wallet.balance;
}
if (wallet is BitcoinWallet) {
balance = wallet.balance;
}
_onCurrentWalletChangeReaction?.reaction?.dispose();
_onCurrentWalletChangeReaction =
reaction<void>((_) => wallet.balance, (dynamic balance) {
if (balance is Balance) {
this.balance = balance;
}
});
}
String _getFiatBalance({double price, String cryptoAmount}) {
if (cryptoAmount == null) {
return '0.00';

View file

@ -231,6 +231,8 @@ abstract class DashboardViewModelBase with Store {
_onMoneroTransactionsUpdate(wallet);
} else {
subname = null;
transactions.clear();
transactions.addAll(wallet.transactionHistory.transactions.values.map(

View file

@ -39,20 +39,11 @@ abstract class ExchangeTradeViewModelBase with Store {
}
items = ObservableList<ExchangeTradeItem>();
items.addAll([
ExchangeTradeItem(
title: S.current.id, data: '${trade.id}', isCopied: true),
ExchangeTradeItem(
title: S.current.amount, data: '${trade.amount}', isCopied: false),
ExchangeTradeItem(
title: S.current.status, data: '${trade.state}', isCopied: false),
ExchangeTradeItem(
title: S.current.widgets_address + ':',
data: trade.inputAddress,
isCopied: true),
]);
_updateItems();
_updateTrade();
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
}
@ -95,8 +86,27 @@ abstract class ExchangeTradeViewModelBase with Store {
}
trade = updatedTrade;
_updateItems();
} catch (e) {
print(e.toString());
}
}
void _updateItems() {
items?.clear();
items.addAll([
ExchangeTradeItem(
title: S.current.id, data: '${trade.id}', isCopied: true),
ExchangeTradeItem(
title: S.current.amount, data: '${trade.amount}', isCopied: false),
ExchangeTradeItem(
title: S.current.status, data: '${trade.state}', isCopied: false),
ExchangeTradeItem(
title: S.current.widgets_address + ':',
data: trade.inputAddress,
isCopied: true),
]);
}
}

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/sync_status.dart';
@ -7,6 +9,7 @@ import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -27,8 +30,8 @@ part 'exchange_view_model.g.dart';
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase(
this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore) {
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
this.tradesStore, this._settingsStore) {
providerList = [
XMRTOExchangeProvider(),
ChangeNowExchangeProvider(),
@ -104,10 +107,6 @@ abstract class ExchangeViewModelBase with Store {
@observable
bool isReceiveAmountEntered;
Limits limits;
NumberFormat _cryptoNumberFormat;
@computed
SyncStatus get status => wallet.syncStatus;
@ -115,6 +114,15 @@ abstract class ExchangeViewModelBase with Store {
ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates;
bool get hasAllAmount =>
wallet.type == WalletType.bitcoin && depositCurrency == wallet.currency;
Limits limits;
NumberFormat _cryptoNumberFormat;
SettingsStore _settingsStore;
@action
void changeProvider({ExchangeProvider provider}) {
this.provider = provider;
@ -264,9 +272,8 @@ abstract class ExchangeViewModelBase with Store {
await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade);
} catch (e) {
tradeState = TradeIsCreatedFailure(
title: provider.title,
error: e.toString());
tradeState =
TradeIsCreatedFailure(title: provider.title, error: e.toString());
}
}
} else {
@ -291,6 +298,22 @@ abstract class ExchangeViewModelBase with Store {
_onPairChange();
}
@action
void calculateDepositAllAmount() {
if (wallet is BitcoinWallet) {
final availableBalance = wallet.balance.availableBalance as int;
final fee = BitcoinWalletBase.feeAmountForPriority(
_settingsStore.transactionPriority);
if (availableBalance < fee || availableBalance == 0) {
return;
}
final amount = availableBalance - fee;
changeDepositAmount(amount: bitcoinAmountToString(amount: amount));
}
}
void updateTemplate() => _exchangeTemplateStore.update();
void addTemplate(

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
@ -37,6 +38,7 @@ abstract class SendViewModelBase with Store {
this._fiatConversationStore, this.transactionDescriptionBox)
: state = InitialExecutionState(),
_cryptoNumberFormat = NumberFormat(),
note = '',
sendAll = false {
final _priority = _settingsStore.transactionPriority;
@ -60,6 +62,9 @@ abstract class SendViewModelBase with Store {
@observable
String address;
@observable
String note;
@observable
bool sendAll;
@ -67,6 +72,50 @@ abstract class SendViewModelBase with Store {
double get estimatedFee =>
_wallet.calculateEstimatedFee(_settingsStore.transactionPriority);
@computed
String get estimatedFeeFiatAmount {
try {
final fiat = calculateFiatAmountRaw(
price: _fiatConversationStore.prices[_wallet.currency],
cryptoAmount: estimatedFee);
return fiat;
} catch (_) {
return '0.00';
}
}
@computed
String get pendingTransactionFiatAmount {
try {
if (pendingTransaction != null) {
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[_wallet.currency],
cryptoAmount: pendingTransaction.amountFormatted);
return fiat;
} else {
return '0.00';
}
} catch (_) {
return '0.00';
}
}
@computed
String get pendingTransactionFeeFiatAmount {
try {
if (pendingTransaction != null) {
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[_wallet.currency],
cryptoAmount: pendingTransaction.feeFormatted);
return fiat;
} else {
return '0.00';
}
} catch (_) {
return '0.00';
}
}
FiatCurrency get fiat => _settingsStore.fiatCurrency;
TransactionPriority get transactionPriority =>
@ -112,6 +161,7 @@ abstract class SendViewModelBase with Store {
cryptoAmount = '';
fiatAmount = '';
address = '';
note = '';
}
@action
@ -131,10 +181,13 @@ abstract class SendViewModelBase with Store {
state = TransactionCommitting();
await pendingTransaction.commit();
if (_settingsStore.shouldSaveRecipientAddress &&
(pendingTransaction.id?.isNotEmpty ?? false)) {
await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction.id, recipientAddress: address));
if (pendingTransaction.id?.isNotEmpty ?? false) {
_settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction.id, recipientAddress: address,
transactionNote: note))
: await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction.id, transactionNote: note));
}
state = TransactionCommitted();
@ -204,7 +257,7 @@ abstract class SendViewModelBase with Store {
switch (_wallet.type) {
case WalletType.bitcoin:
final amount = !sendAll ? double.parse(_amount) : null;
final amount = !sendAll ? _amount : null;
return BitcoinTransactionCredentials(
address, amount, _settingsStore.transactionPriority);

View file

@ -0,0 +1,105 @@
import 'dart:async';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
part 'trade_details_view_model.g.dart';
class TradeDetailsViewModel = TradeDetailsViewModelBase
with _$TradeDetailsViewModel;
abstract class TradeDetailsViewModelBase with Store {
TradeDetailsViewModelBase({Trade tradeForDetails, this.trades}) {
trade = tradeForDetails;
switch (trade.provider) {
case ExchangeProviderDescription.xmrto:
_provider = XMRTOExchangeProvider();
break;
case ExchangeProviderDescription.changeNow:
_provider = ChangeNowExchangeProvider();
break;
case ExchangeProviderDescription.morphToken:
_provider = MorphTokenExchangeProvider(trades: trades);
break;
}
items = ObservableList<StandartListItem>();
_updateItems();
_updateTrade();
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
}
final Box<Trade> trades;
@observable
Trade trade;
@observable
ObservableList<StandartListItem> items;
ExchangeProvider _provider;
Timer _timer;
@action
Future<void> _updateTrade() async {
try {
final updatedTrade = await _provider.findTradeById(id: trade.id);
if (updatedTrade.createdAt == null && trade.createdAt != null) {
updatedTrade.createdAt = trade.createdAt;
}
trade = updatedTrade;
_updateItems();
} catch (e) {
print(e.toString());
}
}
void _updateItems() {
final dateFormat = DateFormatter.withCurrentLocal();
items?.clear();
items.addAll([
StandartListItem(title: S.current.trade_details_id, value: trade.id),
StandartListItem(
title: S.current.trade_details_state,
value: trade.state != null
? trade.state.toString()
: S.current.trade_details_fetching)
]);
if (trade.provider != null) {
items.add(StandartListItem(
title: S.current.trade_details_provider,
value: trade.provider.toString()));
}
if (trade.createdAt != null) {
items.add(StandartListItem(
title: S.current.trade_details_created_at,
value: dateFormat.format(trade.createdAt).toString()));
}
if (trade.from != null && trade.to != null) {
items.add(StandartListItem(
title: S.current.trade_details_pair,
value: '${trade.from.toString()}${trade.to.toString()}'));
}
}
}

View file

@ -0,0 +1,114 @@
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/monero/monero_transaction_info.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'transaction_details_view_model.g.dart';
class TransactionDetailsViewModel = TransactionDetailsViewModelBase
with _$TransactionDetailsViewModel;
abstract class TransactionDetailsViewModelBase with Store {
TransactionDetailsViewModelBase(
{this.transactionInfo,
this.transactionDescriptionBox,
this.settingsStore})
: items = [] {
showRecipientAddress = settingsStore?.shouldSaveRecipientAddress ?? false;
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
if (tx is MoneroTransactionInfo) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date)),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
StandartListItem(
title: S.current.transaction_details_fee, value: tx.feeFormatted()),
];
if (tx.key?.isNotEmpty ?? null) {
_items.add(
StandartListItem(title: S.current.transaction_key, value: tx.key));
}
items.addAll(_items);
}
if (tx is BitcoinTransactionInfo) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date)),
StandartListItem(
title: S.current.confirmations,
value: tx.confirmations?.toString()),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted())
];
items.addAll(_items);
}
if (showRecipientAddress) {
final recipientAddress = transactionDescriptionBox.values
.firstWhere((val) => val.id == transactionInfo.id, orElse: () => null)
?.recipientAddress;
if (recipientAddress?.isNotEmpty ?? false) {
items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: recipientAddress));
}
}
final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == transactionInfo.id,
orElse: () => TransactionDescription(id: transactionInfo.id));
items.add(TextFieldListItem(
title: S.current.note_tap_to_change,
value: description.note,
onSubmitted: (value) {
description.transactionNote = value;
if (description.isInBox) {
description.save();
} else {
transactionDescriptionBox.add(description);
}
}));
}
final TransactionInfo transactionInfo;
final Box<TransactionDescription> transactionDescriptionBox;
final SettingsStore settingsStore;
final List<TransactionDetailsListItem> items;
bool showRecipientAddress;
}

View file

@ -11,7 +11,7 @@ description: Cake Wallet.
# 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: 4.1.0+26
version: 4.1.0+28
environment:
sdk: ">=2.7.0 <3.0.0"

View file

@ -212,7 +212,7 @@
"send_name" : "Name",
"send_got_it" : "Ich habs",
"send_sending" : "Senden...",
"send_success" : "Ihr Monero wurde erfolgreich gesendet",
"send_success" : "Ihr ${crypto} wurde erfolgreich gesendet",
"settings_title" : "die Einstellungen",
@ -282,6 +282,7 @@
"transaction_details_date" : "Datum",
"transaction_details_height" : "Höhe",
"transaction_details_amount" : "Menge",
"transaction_details_fee" : "Gebühr",
"transaction_details_copied" : "${title} in die Zwischenablage kopiert",
"transaction_details_recipient_address" : "Empfängeradresse",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Bitte warten Sie, bis Ihre Brieftasche synchronisiert ist",
"pre_seed_title" : "WICHTIG",
"pre_seed_description" : "Auf der nächsten Seite sehen Sie eine Reihe von 25 Wörtern. Dies ist Ihr einzigartiger und privater Samen und der EINZIGE Weg, um Ihren Geldbeutel im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, es aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet App aufzubewahren.",
"pre_seed_description" : "Auf der nächsten Seite sehen Sie eine Reihe von ${words} Wörtern. Dies ist Ihr einzigartiger und privater Samen und der EINZIGE Weg, um Ihren Geldbeutel im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, es aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet App aufzubewahren.",
"pre_seed_button_text" : "Ich verstehe. Zeig mir meinen Samen",
"xmr_to_error" : "XMR.TO-Fehler",
@ -428,5 +429,11 @@
"color_theme" : "Farbthema",
"light_theme" : "Licht",
"bright_theme" : "Hell",
"dark_theme" : "Dunkel"
"dark_theme" : "Dunkel",
"enter_your_note" : "Geben Sie Ihre Notiz ein…",
"note_optional" : "Hinweis (optional)",
"note_tap_to_change" : "Hinweis (zum Ändern tippen)",
"transaction_key" : "Transaktionsschlüssel",
"confirmations" : "Bestätigungen",
"recipient_address" : "Empfängeradresse"
}

View file

@ -212,7 +212,7 @@
"send_name" : "Name",
"send_got_it" : "Got it",
"send_sending" : "Sending...",
"send_success" : "Your Monero was successfully sent",
"send_success" : "Your ${crypto} was successfully sent",
"settings_title" : "Settings",
@ -282,6 +282,7 @@
"transaction_details_date" : "Date",
"transaction_details_height" : "Height",
"transaction_details_amount" : "Amount",
"transaction_details_fee" : "Fee",
"transaction_details_copied" : "${title} copied to Clipboard",
"transaction_details_recipient_address" : "Recipient address",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Please wait until your wallet is synchronized",
"pre_seed_title" : "IMPORTANT",
"pre_seed_description" : "On the next page you will see a series of 25 words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.",
"pre_seed_description" : "On the next page you will see a series of ${words} words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.",
"pre_seed_button_text" : "I understand. Show me my seed",
"xmr_to_error" : "XMR.TO error",
@ -428,5 +429,11 @@
"color_theme" : "Color theme",
"light_theme" : "Light",
"bright_theme" : "Bright",
"dark_theme" : "Dark"
"dark_theme" : "Dark",
"enter_your_note" : "Enter your note…",
"note_optional" : "Note (optional)",
"note_tap_to_change" : "Note (tap to change)",
"transaction_key" : "Transaction Key",
"confirmations" : "Confirmations",
"recipient_address" : "Recipient address"
}

View file

@ -212,7 +212,7 @@
"send_name" : "Nombre",
"send_got_it" : "Entendido",
"send_sending" : "Enviando...",
"send_success" : "Su Monero fue enviado con éxito",
"send_success" : "Su ${crypto} fue enviado con éxito",
"settings_title" : "Configuraciones",
@ -282,6 +282,7 @@
"transaction_details_date" : "Fecha",
"transaction_details_height" : "Altura",
"transaction_details_amount" : "Cantidad",
"transaction_details_fee" : "Cuota",
"transaction_details_copied" : "${title} Copiado al portapapeles",
"transaction_details_recipient_address" : "Dirección del receptor",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Espere hasta que su billetera esté sincronizada",
"pre_seed_title" : "IMPORTANTE",
"pre_seed_description" : "En la página siguiente verá una serie de 25 palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet.",
"pre_seed_description" : "En la página siguiente verá una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet.",
"pre_seed_button_text" : "Entiendo. Muéstrame mi semilla",
"xmr_to_error" : "Error de XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Tema de color",
"light_theme" : "Ligera",
"bright_theme" : "Brillante",
"dark_theme" : "Oscura"
"dark_theme" : "Oscura",
"enter_your_note" : "Ingresa tu nota…",
"note_optional" : "Nota (opcional)",
"note_tap_to_change" : "Nota (toque para cambiar)",
"transaction_key" : "Clave de transacción",
"confirmations" : "Confirmaciones",
"recipient_address" : "Dirección del receptor"
}

View file

@ -212,7 +212,7 @@
"send_name" : "नाम",
"send_got_it" : "समझ गया",
"send_sending" : "भेजना...",
"send_success" : "आपका Monero सफलतापूर्वक भेजा गया",
"send_success" : "आपका ${crypto} सफलतापूर्वक भेजा गया",
"settings_title" : "सेटिंग्स",
@ -282,6 +282,7 @@
"transaction_details_date" : "तारीख",
"transaction_details_height" : "ऊंचाई",
"transaction_details_amount" : "रकम",
"transaction_details_fee" : "शुल्क",
"transaction_details_copied" : "${title} क्लिपबोर्ड पर नकल",
"transaction_details_recipient_address" : "प्राप्तकर्ता का पता",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "कृपया प्रतीक्षा करें जब तक आपका बटुआ सिंक्रनाइज़ नहीं किया जाता है",
"pre_seed_title" : "महत्वपूर्ण",
"pre_seed_description" : "अगले पेज पर आपको 25 शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।",
"pre_seed_description" : "अगले पेज पर आपको ${words} शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।",
"pre_seed_button_text" : "मै समझता हुँ। मुझे अपना बीज दिखाओ",
"xmr_to_error" : "XMR.TO त्रुटि",
@ -428,5 +429,11 @@
"color_theme" : "रंग विषय",
"light_theme" : "रोशनी",
"bright_theme" : "उज्ज्वल",
"dark_theme" : "अंधेरा"
"dark_theme" : "अंधेरा",
"enter_your_note" : "अपना नोट दर्ज करें ...",
"note_optional" : "नोट (वैकल्पिक)",
"note_tap_to_change" : "नोट (टैप टू चेंज)",
"transaction_key" : "लेन-देन की",
"confirmations" : "पुष्टिकरण",
"recipient_address" : "प्राप्तकर्ता का पता"
}

View file

@ -212,7 +212,7 @@
"send_name" : "名前",
"send_got_it" : "とった",
"send_sending" : "送信...",
"send_success" : "Moneroが送信されました",
"send_success" : "${crypto}が送信されました",
"settings_title" : "設定",
@ -282,6 +282,7 @@
"transaction_details_date" : "日付",
"transaction_details_height" : "高さ",
"transaction_details_amount" : "量",
"transaction_details_fee" : "費用",
"transaction_details_copied" : "${title} クリップボードにコピーしました",
"transaction_details_recipient_address" : "受取人の住所",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "ウォレットが同期されるまでお待ちください",
"pre_seed_title" : "重要",
"pre_seed_description" : "次のページでは、一連の25語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。",
"pre_seed_description" : "次のページでは、一連の${words}語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。",
"pre_seed_button_text" : "わかります。 種を見せて",
"xmr_to_error" : "XMR.TOエラー",
@ -428,5 +429,11 @@
"color_theme" : "カラーテーマ",
"light_theme" : "光",
"bright_theme" : "明るい",
"dark_theme" : "闇"
"dark_theme" : "闇",
"enter_your_note" : "メモを入力してください…",
"note_optional" : "注(オプション)",
"note_tap_to_change" : "注(タップして変更)",
"transaction_key" : "トランザクションキー",
"confirmations" : "確認",
"recipient_address" : "受信者のアドレス"
}

View file

@ -212,7 +212,7 @@
"send_name" : "이름",
"send_got_it" : "알았다",
"send_sending" : "배상...",
"send_success" : "Monero가 성공적으로 전송되었습니다",
"send_success" : "${crypto}가 성공적으로 전송되었습니다",
"settings_title" : "설정",
@ -282,6 +282,7 @@
"transaction_details_date" : "날짜",
"transaction_details_height" : "신장",
"transaction_details_amount" : "양",
"transaction_details_fee" : "회비",
"transaction_details_copied" : "${title} 클립 보드에 복사",
"transaction_details_recipient_address" : "받는 사람 주소",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "지갑이 동기화 될 때까지 기다리십시오",
"pre_seed_title" : "중대한",
"pre_seed_description" : "다음 페이지에서 25 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.",
"pre_seed_description" : "다음 페이지에서 ${words} 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.",
"pre_seed_button_text" : "이해 했어요. 내 씨앗을 보여줘",
"xmr_to_error" : "XMR.TO 오류",
@ -428,5 +429,11 @@
"color_theme" : "색상 테마",
"light_theme" : "빛",
"bright_theme" : "선명한",
"dark_theme" : "어두운"
"dark_theme" : "어두운",
"enter_your_note" : "메모를 입력하세요…",
"note_optional" : "참고 (선택 사항)",
"note_tap_to_change" : "메모 (변경하려면 탭하세요)",
"transaction_key" : "거래 키",
"confirmations" : "확인",
"recipient_address" : "받는 사람 주소"
}

View file

@ -212,7 +212,7 @@
"send_name" : "Naam",
"send_got_it" : "Ik snap het",
"send_sending" : "Bezig met verzenden...",
"send_success" : "Uw Monero is succesvol verzonden",
"send_success" : "Uw ${crypto} is succesvol verzonden",
"settings_title" : "Instellingen",
@ -282,6 +282,7 @@
"transaction_details_date" : "Datum",
"transaction_details_height" : "Hoogte",
"transaction_details_amount" : "Bedrag",
"transaction_details_fee" : "Vergoeding",
"transaction_details_copied" : "${title} gekopieerd naar het klembord",
"transaction_details_recipient_address" : "Adres van de ontvanger",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Wacht tot uw portemonnee is gesynchroniseerd",
"pre_seed_title" : "BELANGRIJK",
"pre_seed_description" : "Op de volgende pagina ziet u een reeks van 25 woorden. Dit is uw unieke en persoonlijke zaadje en het is de ENIGE manier om uw portemonnee te herstellen in geval van verlies of storing. Het is JOUW verantwoordelijkheid om het op te schrijven en op een veilige plaats op te slaan buiten de Cake Wallet app.",
"pre_seed_description" : "Op de volgende pagina ziet u een reeks van ${words} woorden. Dit is uw unieke en persoonlijke zaadje en het is de ENIGE manier om uw portemonnee te herstellen in geval van verlies of storing. Het is JOUW verantwoordelijkheid om het op te schrijven en op een veilige plaats op te slaan buiten de Cake Wallet app.",
"pre_seed_button_text" : "Ik begrijp het. Laat me mijn zaad zien",
"xmr_to_error" : "XMR.TO-fout",
@ -428,5 +429,11 @@
"color_theme" : "Kleur thema",
"light_theme" : "Licht",
"bright_theme" : "Helder",
"dark_theme" : "Donker"
"dark_theme" : "Donker",
"enter_your_note" : "Voer uw notitie in ...",
"note_optional" : "Opmerking (optioneel)",
"note_tap_to_change" : "Opmerking (tik om te wijzigen)",
"transaction_key" : "Transactiesleutel",
"confirmations" : "Bevestigingen",
"recipient_address" : "Adres ontvanger"
}

View file

@ -212,7 +212,7 @@
"send_name" : "Imię",
"send_got_it" : "Rozumiem",
"send_sending" : "Wysyłanie...",
"send_success" : "Twoje Monero zostało pomyślnie wysłane",
"send_success" : "Twoje ${crypto} zostało pomyślnie wysłane",
"settings_title" : "Ustawienia",
@ -282,6 +282,7 @@
"transaction_details_date" : "Data",
"transaction_details_height" : "Wysokość",
"transaction_details_amount" : "Ilość",
"transaction_details_fee" : "Opłata",
"transaction_details_copied" : "${title} skopiowane do schowka",
"transaction_details_recipient_address" : "Adres odbiorcy",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Poczekaj, aż portfel zostanie zsynchronizowany",
"pre_seed_title" : "WAŻNY",
"pre_seed_description" : "Na następnej stronie zobaczysz serię 25 słów. To jest Twoje unikalne i prywatne ziarno i jest to JEDYNY sposób na odzyskanie portfela w przypadku utraty lub awarii. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu poza aplikacją Cake Wallet.",
"pre_seed_description" : "Na następnej stronie zobaczysz serię ${words} słów. To jest Twoje unikalne i prywatne ziarno i jest to JEDYNY sposób na odzyskanie portfela w przypadku utraty lub awarii. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu poza aplikacją Cake Wallet.",
"pre_seed_button_text" : "Rozumiem. Pokaż mi moje nasienie",
"xmr_to_error" : "Pomyłka XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Motyw kolorystyczny",
"light_theme" : "Lekki",
"bright_theme" : "Jasny",
"dark_theme" : "Ciemny"
"dark_theme" : "Ciemny",
"enter_your_note" : "Wpisz notatkę…",
"note_optional" : "Notatka (opcjonalnie)",
"note_tap_to_change" : "Notatka (dotknij, aby zmienić)",
"transaction_key" : "Klucz transakcji",
"confirmations" : "Potwierdzenia",
"recipient_address" : "Adres odbiorcy"
}

View file

@ -212,7 +212,7 @@
"send_name" : "Nome",
"send_got_it" : "Entendi",
"send_sending" : "Enviando...",
"send_success" : "Seu Monero foi enviado com sucesso",
"send_success" : "Seu ${crypto} foi enviado com sucesso",
"settings_title" : "Configurações",
@ -282,6 +282,7 @@
"transaction_details_date" : "Data",
"transaction_details_height" : "Altura",
"transaction_details_amount" : "Quantia",
"transaction_details_fee" : "Taxa",
"transaction_details_copied" : "${title} copiados para a área de transferência",
"transaction_details_recipient_address" : "Endereço do destinatário",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Por favor, espere até que sua carteira seja sincronizada",
"pre_seed_title" : "IMPORTANTE",
"pre_seed_description" : "Na próxima página, você verá uma série de 25 palavras. Esta é a sua semente única e privada e é a ÚNICA maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É SUA responsabilidade anotá-lo e armazená-lo em um local seguro fora do aplicativo Cake Wallet.",
"pre_seed_description" : "Na próxima página, você verá uma série de ${words} palavras. Esta é a sua semente única e privada e é a ÚNICA maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É SUA responsabilidade anotá-lo e armazená-lo em um local seguro fora do aplicativo Cake Wallet.",
"pre_seed_button_text" : "Compreendo. Me mostre minha semente",
"xmr_to_error" : "Erro XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Tema de cor",
"light_theme" : "Luz",
"bright_theme" : "Brilhante",
"dark_theme" : "Sombria"
"dark_theme" : "Sombria",
"enter_your_note" : "Insira sua nota ...",
"note_optional" : "Nota (opcional)",
"note_tap_to_change" : "Nota (toque para alterar)",
"transaction_key" : "Chave de transação",
"confirmations" : "Confirmações",
"recipient_address" : "Endereço do destinatário"
}

View file

@ -212,7 +212,7 @@
"send_name" : "Имя",
"send_got_it" : "Понял",
"send_sending" : "Отправка...",
"send_success" : "Ваш Monero был успешно отправлен",
"send_success" : "Ваш ${crypto} был успешно отправлен",
"settings_title" : "Настройки",
@ -282,6 +282,7 @@
"transaction_details_date" : "Дата",
"transaction_details_height" : "Высота",
"transaction_details_amount" : "Сумма",
"transaction_details_fee" : "Комиссия",
"transaction_details_copied" : "${title} скопировано в буфер обмена",
"transaction_details_recipient_address" : "Адрес получателя",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Подождите, пока ваш кошелек синхронизируется",
"pre_seed_title" : "ВАЖНО",
"pre_seed_description" : "На следующей странице вы увидите серию из 25 слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.",
"pre_seed_description" : "На следующей странице вы увидите серию из ${words} слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.",
"pre_seed_button_text" : "Понятно. Покажите мнемоническую фразу",
"xmr_to_error" : "Ошибка XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Цветовая тема",
"light_theme" : "Светлая",
"bright_theme" : "Яркая",
"dark_theme" : "Темная"
"dark_theme" : "Темная",
"enter_your_note" : "Введите примечание…",
"note_optional" : "Примечание (необязательно)",
"note_tap_to_change" : "Примечание (нажмите для изменения)",
"transaction_key" : "Ключ транзакции",
"confirmations" : "Подтверждения",
"recipient_address" : "Адрес получателя"
}

View file

@ -212,7 +212,7 @@
"send_name" : "Ім'я",
"send_got_it" : "Зрозумів",
"send_sending" : "Відправлення...",
"send_success" : "Ваш Monero успішно надісланий",
"send_success" : "Ваш ${crypto} успішно надісланий",
"settings_title" : "Налаштування",
@ -282,6 +282,7 @@
"transaction_details_date" : "Дата",
"transaction_details_height" : "Висота",
"transaction_details_amount" : "Сума",
"transaction_details_fee" : "Комісія",
"transaction_details_copied" : "${title} скопійовано в буфер обміну",
"transaction_details_recipient_address" : "Адреса отримувача",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Зачекайте, поки ваш гаманець не синхронізується",
"pre_seed_title" : "ВАЖЛИВО",
"pre_seed_description" : "На наступній сторінці ви побачите серію з 25 слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.",
"pre_seed_description" : "На наступній сторінці ви побачите серію з ${words} слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.",
"pre_seed_button_text" : "Зрозуміло. Покажіть мнемонічну фразу",
"xmr_to_error" : "Помилка XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Кольорова тема",
"light_theme" : "Світла",
"bright_theme" : "Яскрава",
"dark_theme" : "Темна"
"dark_theme" : "Темна",
"enter_your_note" : "Введіть примітку…",
"note_optional" : "Примітка (необов’язково)",
"note_tap_to_change" : "Примітка (натисніть для зміни)",
"transaction_key" : "Ключ транзакції",
"confirmations" : "Підтвердження",
"recipient_address" : "Адреса одержувача"
}

View file

@ -212,7 +212,7 @@
"send_name" : "名稱",
"send_got_it" : "得到它了",
"send_sending" : "正在發送...",
"send_success" : "你Monero已成功發送",
"send_success" : "你${crypto}已成功發送",
"settings_title" : "设定值",
@ -282,6 +282,7 @@
"transaction_details_date" : "日期",
"transaction_details_height" : "高度",
"transaction_details_amount" : "量",
"transaction_details_fee" : "費用",
"transaction_details_copied" : "${title} 复制到剪贴板",
"transaction_details_recipient_address" : "收件人地址",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "請等待,直到您的錢包同步",
"pre_seed_title" : "重要",
"pre_seed_description" : "在下一頁上,您將看到一系列25個單詞。 這是您獨特的私人種子,是丟失或出現故障時恢復錢包的唯一方法。 您有責任將其寫下並存儲在Cake Wallet應用程序外部的安全地方。",
"pre_seed_description" : "在下一頁上,您將看到一系列${words}個單詞。 這是您獨特的私人種子,是丟失或出現故障時恢復錢包的唯一方法。 您有責任將其寫下並存儲在Cake Wallet應用程序外部的安全地方。",
"pre_seed_button_text" : "我明白。 給我看我的種子",
"xmr_to_error" : "XMR.TO錯誤",
@ -428,5 +429,11 @@
"color_theme" : "顏色主題",
"light_theme" : "光",
"bright_theme" : "亮",
"dark_theme" : "黑暗"
"dark_theme" : "黑暗",
"enter_your_note" : "輸入您的筆記...",
"note_optional" : "注意(可選)",
"note_tap_to_change" : "注意(輕按即可更改)",
"transaction_key" : "交易密碼",
"confirmations" : "確認書",
"recipient_address" : "收件人地址"
}