Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-328-Restore-wallet-from-QRCode-and-sweep-all-funds-in-a-new-wallet

This commit is contained in:
Blazebrain 2023-05-16 08:30:29 +01:00
commit d3915366a7
64 changed files with 776 additions and 685 deletions

View file

@ -5,9 +5,10 @@
## Links
* Website: https://cakewallet.com
* App Store: https://cakewallet.com/ios
* App Store (iOS / MacOS): https://cakewallet.com/ios
* Google Play: https://cakewallet.com/gp
* APK: https://github.com/cake-tech/cake_wallet/releases
* Linux: https://github.com/cake-tech/cake_wallet/releases
## Features
@ -15,18 +16,22 @@
* Completely noncustodial. *Your keys, your coins.*
* Built-in exchange for dozens of pairs
* Buy cryptocurrency with credit/debit/bank
* Easily pay cryptocurrency invoices with fixed rate exchanges
* Buy cryptocurrency (BTC/LTC/XMR) with credit/debit/bank
* Sell cryptocurrency by bank transfer
* Purchase gift cards at a discount using only an email with [Cake Pay](https://cakepay.com), available in-app
* Scan QR codes for easy cryptocurrency transfers
* Create several wallets
* Select your own custom nodes/servers
* Address book
* Backup to external location or iCloud
* Send to OpenAlias, Unstoppable Domains, Yats, and FIO Crypto Handles
* Set custom fee levels
* Set desired network fee level
* Store local transaction notes
* Extremely simple user experience
* Convenient exchange and sending templates for recurring payments
* Create donation links and invoices in the receive screen
* Robust privacy settings (eg: Tor-only connections)
### Monero Specific Features
@ -34,13 +39,13 @@
* Full support for Monero subaddresses and accounts
* Specify restore height for faster syncing
* Specify multiple recipients for batch sending
* Optionally set Monero nodes as trusted for faster syncing
### Bitcoin Specific Features
* Bitcoin coin control (specify specific outputs to spend)
* Automatically generate new addresses
* Specify multiple recipients for batch sending
* Buy BTC with over a dozen fiat currencies
* Sell BTC for USD
### Litecoin Specific Features
@ -48,7 +53,6 @@
* Litecoin coin control (specify specific outputs to spend)
* Automatically generate new addresses
* Specify multiple recipients for batch sending
* Buy LTC with over a dozen fiat currencies
### Haven Specific Features
@ -63,7 +67,7 @@
## Links
* Website: https://monero.com
* App Store: https://apps.apple.com/app/id1601990386
* App Store (iOS): https://apps.apple.com/app/id1601990386
* Google Play: https://play.google.com/store/apps/details?id=com.monero.app
* APK: https://github.com/cake-tech/cake_wallet/releases
@ -71,6 +75,8 @@
We have 24/7 free support. Please contact support@cakewallet.com
We have excellent user guides, which are also open-source and open for contributions: https://guides.cakewallet.com
# Build Instructions
More instructions to follow
@ -136,3 +142,7 @@ The only parts to be translated, if needed, are the values m and s after the var
3. Add the raw mapping underneath in `lib/entities/fiat_currency.dart` following the same format as the others.
4. Add a flag of the issuing country or organization to `assets/images/flags/XXXX.png`, replacing XXXX with the ISO 3166-1 alpha-3 code used above (eg: `usa.png`, `eur.png`). Do not add this if the flag with the same name already exists. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides.
---
Copyright (C) 2018-2023 Cake Labs LLC

View file

@ -1 +1,3 @@
Fix Restore from QR code
Reliability fixes for PIN login, transaction appearance, keyboard inputs, and QR codes
Show amount received by each Monero account in account overview
Other bugfixes

View file

@ -1,4 +1,3 @@
Fix for QR codes
Fix for creating sub-addresses
Fix Add/Edit nodes
Fix issues with text/amount fields
Reliability fixes for PIN login, transaction appearance, keyboard inputs, and QR codes
Show amount received by each Monero account in account overview
Other bugfixes

View file

@ -88,7 +88,7 @@ class ElectrumClient {
unterminatedString = '';
}
} on TypeError catch (e) {
if (!e.toString().contains('Map<String, Object>') || !e.toString().contains('Map<String, dynamic>')) {
if (!e.toString().contains('Map<String, Object>') && !e.toString().contains('Map<String, dynamic>')) {
return;
}

View file

@ -113,7 +113,7 @@ PODS:
- OrderedSet (~> 5.0)
- flutter_mailer (0.0.1):
- Flutter
- flutter_secure_storage (3.3.1):
- flutter_secure_storage (6.0.0):
- Flutter
- in_app_review (0.2.0):
- Flutter
@ -264,19 +264,19 @@ SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
platform_device_id: 81b3e2993881f87d0c82ef151dc274df4869aef5
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
SwiftProtobuf: afced68785854575756db965e9da52bbf3dc45e7
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a

View file

@ -52,16 +52,6 @@
<string>bitcoin-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>bitcoin_wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin_wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
@ -82,16 +72,6 @@
<string>monero-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>monero_wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>monero_wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
@ -112,16 +92,6 @@
<string>litecoin-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>litecoin_wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>litecoin_wallet</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
class OnRamperBuyProvider {
@ -13,7 +14,16 @@ class OnRamperBuyProvider {
static const _baseUrl = 'buy.onramper.com';
static String get _apiKey => secrets.onramperApiKey;
String get _apiKey => secrets.onramperApiKey;
String get _normalizeCryptoCurrency {
switch (_wallet.currency) {
case CryptoCurrency.ltc:
return "LTC_LITECOIN";
default:
return _wallet.currency.title;
}
}
Uri requestUrl() {
String primaryColor,
@ -53,7 +63,7 @@ class OnRamperBuyProvider {
return Uri.https(_baseUrl, '', <String, dynamic>{
'apiKey': _apiKey,
'defaultCrypto': _wallet.currency.title,
'defaultCrypto': _normalizeCryptoCurrency,
'defaultFiat': _settingsStore.fiatCurrency.title,
'wallets': '${_wallet.currency.title}:${_wallet.walletAddresses.address}',
'supportSell': "false",

View file

@ -1,4 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cw_core/crypto_currency.dart';
@ -7,6 +7,9 @@ class AddressValidator extends TextValidator {
AddressValidator({required CryptoCurrency type})
: super(
errorMessage: S.current.error_text_address,
useAdditionalValidation: type == CryptoCurrency.btc
? bitcoin.Address.validateAddress
: null,
pattern: getPattern(type),
length: getLength(type));
@ -18,8 +21,7 @@ class AddressValidator extends TextValidator {
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.btc:
return '^1[0-9a-zA-Z]{32}\$|^1[0-9a-zA-Z]{33}\$|^3[0-9a-zA-Z]{32}\$'
'|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{39}\$|^bc1[0-9a-zA-Z]{59}\$';
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.usdc:

View file

@ -210,6 +210,8 @@ class BackupService {
final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?;
final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?;
final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?;
final disableBuy = data[PreferencesKey.disableBuyKey] as bool?;
final disableSell = data[PreferencesKey.disableSellKey] as bool?;
final currentTransactionPriorityKeyLegacy = data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?;
final allowBiometricalAuthentication = data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?;
final currentBitcoinElectrumSererId = data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?;
@ -251,6 +253,16 @@ class BackupService {
PreferencesKey.isAppSecureKey,
isAppSecure);
if (disableBuy != null)
await _sharedPreferences.setBool(
PreferencesKey.disableBuyKey,
disableBuy);
if (disableSell != null)
await _sharedPreferences.setBool(
PreferencesKey.disableSellKey,
disableSell);
if (currentTransactionPriorityKeyLegacy != null)
await _sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKeyLegacy,
@ -421,6 +433,10 @@ class BackupService {
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
PreferencesKey.disableBuyKey: _sharedPreferences
.getBool(PreferencesKey.disableBuyKey),
PreferencesKey.disableSellKey: _sharedPreferences
.getBool(PreferencesKey.disableSellKey),
PreferencesKey.isDarkThemeLegacy:
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
PreferencesKey.currentPinLength:

View file

@ -1,9 +1,8 @@
import 'package:flutter/foundation.dart';
abstract class Validator<T> {
Validator({required this.errorMessage});
Validator({required this.errorMessage, this.useAdditionalValidation});
final String errorMessage;
final bool Function(T)? useAdditionalValidation;
bool isValid(T? value);
@ -11,14 +10,17 @@ abstract class Validator<T> {
}
class TextValidator extends Validator<String> {
TextValidator(
{this.minLength,
TextValidator({
bool Function(String)? useAdditionalValidation,
this.minLength,
this.maxLength,
this.pattern,
String errorMessage = '',
this.length,
this.isAutovalidate = false})
: super(errorMessage: errorMessage);
this.isAutovalidate = false,
}) : super(
errorMessage: errorMessage,
useAdditionalValidation: useAdditionalValidation);
final int? minLength;
final int? maxLength;
@ -32,11 +34,26 @@ class TextValidator extends Validator<String> {
return isAutovalidate ? true : false;
}
return value.length > (minLength ?? 0) &&
(length?.contains(value.length) ?? true) &&
((maxLength ?? 0) > 0 ? (value.length <= maxLength!) : true) &&
(pattern != null ? match(value) : true);
final greaterThanMinLength = value.length > (minLength ?? 0);
if (!greaterThanMinLength) return false;
final lengthMatched = length?.contains(value.length) ?? true;
if (!lengthMatched) return false;
final lowerThanMaxLength =
(maxLength ?? 0) > 0 ? (value.length <= maxLength!) : true;
if (!lowerThanMaxLength) return false;
if (pattern == null) return true;
final valueMatched = match(value);
final valueValidated = useAdditionalValidation != null
? useAdditionalValidation!(value) || valueMatched
: valueMatched;
return valueValidated;
}
bool match(String value) => pattern != null ? RegExp(pattern!).hasMatch(value) : false;
bool match(String value) =>
pattern != null ? RegExp(pattern!).hasMatch(value) : false;
}

View file

@ -47,24 +47,24 @@ class MainActions {
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
if (viewModel.isEnabledBuyAction) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.onramperPage);
} else {
final uri = getIt
.get<OnRamperBuyProvider>()
.requestUrl();
final uri = getIt.get<OnRamperBuyProvider>().requestUrl();
await launchUrl(uri);
}
}
break;
case WalletType.monero:
if (viewModel.isEnabledBuyAction) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.payfuraPage);
} else {
final uri = getIt
.get<PayfuraBuyProvider>()
.requestUrl();
final uri = getIt.get<PayfuraBuyProvider>().requestUrl();
await launchUrl(uri);
}
}
break;
default:
await showPopUp<void>(
@ -118,12 +118,14 @@ class MainActions {
switch (walletType) {
case WalletType.bitcoin:
if (viewModel.isEnabledSellAction) {
final moonPaySellProvider = MoonPaySellProvider();
final uri = await moonPaySellProvider.requestUrl(
currency: viewModel.wallet.currency,
refundWalletAddress: viewModel.wallet.walletAddresses.address,
);
await launchUrl(uri);
}
break;
default:
await showPopUp<void>(

View file

@ -10,6 +10,8 @@ class PreferencesKey {
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
static const isAppSecureKey = 'is_app_secure';
static const disableBuyKey = 'disable_buy';
static const disableSellKey = 'disable_sell';
static const currentFiatApiModeKey = 'current_fiat_api_mode';
static const allowBiometricalAuthenticationKey =
'allow_biometrical_authentication';

View file

@ -169,6 +169,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true);
} else if (isSingleCoin) {
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<WalletRestorePage>(
param1: availableWalletTypes.first
));
@ -188,6 +189,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreWallet:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<WalletRestorePage>(
param1: settings.arguments as WalletType));
@ -509,7 +511,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.anonPayInvoicePage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage:
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;

View file

@ -1,13 +1,11 @@
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/contact_name_validator.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
@ -33,8 +31,8 @@ class ContactPage extends BasePage {
_addressController
.addListener(() => contactViewModel.address = _addressController.text);
autorun((_) =>
_currencyTypeController.text = contactViewModel.currency?.toString()??'');
autorun((_) => _currencyTypeController.text =
contactViewModel.currency?.toString() ?? '');
}
@override
@ -61,7 +59,8 @@ class ContactPage extends BasePage {
}
});
return ScrollableWithBottomSection(
return Observer(
builder: (_) => ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Form(
key: _formKey,
@ -90,22 +89,25 @@ class ContactPage extends BasePage {
),
),
),
if (contactViewModel.currency != null)
Padding(
padding: EdgeInsets.only(top: 20),
child: Observer(
builder: (_) => AddressTextField(
child: AddressTextField(
controller: _addressController,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
],
buttonColor: Theme.of(context).accentTextTheme!.headline3!.color!,
buttonColor:
Theme.of(context).accentTextTheme!.headline3!.color!,
iconColor: PaletteDark.gray,
borderColor: Theme.of(context).primaryTextTheme!.headline6!.backgroundColor!,
validator: TextValidator()
// AddressValidator(
// type: contactViewModel.currency),
)),
borderColor: Theme.of(context)
.primaryTextTheme!
.headline6!
.backgroundColor!,
validator:
AddressValidator(type: contactViewModel.currency!),
),
)
],
),
@ -130,27 +132,32 @@ class ContactPage extends BasePage {
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
if (_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
return;
}
await contactViewModel.save();
},
text: S.of(context).save,
color: Theme.of(context).accentTextTheme!.bodyText1!.color!,
color: Theme.of(context)
.accentTextTheme!
.bodyText1!
.color!,
textColor: Colors.white,
isDisabled: !contactViewModel.isReady)))
],
));
)),
);
}
void _presentCurrencyPicker(BuildContext context) {
showPopUp<void>(
builder: (_) => CurrencyPicker(
selectedAtIndex:
contactViewModel.currency != null
? contactViewModel.currencies.indexOf(contactViewModel.currency!)
: 0,
selectedAtIndex: contactViewModel.currency != null
? contactViewModel.currencies
.indexOf(contactViewModel.currency!)
: -1,
items: contactViewModel.currencies,
title: S.of(context).please_select,
hintText: S.of(context).search_currency,

View file

@ -96,7 +96,10 @@ class AddressPage extends BasePage {
@override
Widget middle(BuildContext context) =>
PresentReceiveOptionPicker(receiveOptionViewModel: receiveOptionViewModel);
PresentReceiveOptionPicker(
receiveOptionViewModel: receiveOptionViewModel,
hasWhiteBackground: currentTheme.type == ThemeType.light,
);
@override
Widget Function(BuildContext, Widget) get rootWrapper =>

View file

@ -1,13 +1,9 @@
import 'dart:ui';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
@ -20,26 +16,26 @@ class FilterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
const sectionDivider = const SectionDivider();
return AlertBackground(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
return PickerWrapperWidget(
children: [
Padding(
padding: EdgeInsets.only(left: 24, right: 24, top: 24),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(24)),
child: Container(
color: Theme.of(context).textTheme!.bodyText1!.decorationColor!,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(24.0),
child: Text(
S.of(context).filter_by,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.overline!.color!,
color: Theme.of(context)
.primaryTextTheme
.overline!
.color!,
fontSize: 16,
fontFamily: 'Lato',
decoration: TextDecoration.none,
@ -54,17 +50,23 @@ class FilterWidget extends StatelessWidget {
itemCount: dashboardViewModel.filterItems.length,
separatorBuilder: (context, _) => sectionDivider,
itemBuilder: (_, index1) {
final title = dashboardViewModel.filterItems.keys.elementAt(index1);
final section = dashboardViewModel.filterItems.values.elementAt(index1);
final title = dashboardViewModel.filterItems.keys
.elementAt(index1);
final section = dashboardViewModel.filterItems.values
.elementAt(index1);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 20, left: 24, right: 24),
padding:
EdgeInsets.only(top: 20, left: 24, right: 24),
child: Text(
title,
style: TextStyle(
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
color: Theme.of(context)
.primaryTextTheme!
.headline6!
.color!,
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.bold,
@ -83,9 +85,11 @@ class FilterWidget extends StatelessWidget {
value: item.value(),
caption: item.caption,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
borderColor:
Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) => item.onChanged(),
onChanged: (value) =>
item.onChanged(),
));
return FilterTile(child: content);
},
@ -97,12 +101,8 @@ class FilterWidget extends StatelessWidget {
]),
),
),
),
)
],
),
AlertCloseButton()
],
),
);
}
}

View file

@ -9,14 +9,22 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
class PresentReceiveOptionPicker extends StatelessWidget {
PresentReceiveOptionPicker({required this.receiveOptionViewModel});
PresentReceiveOptionPicker(
{required this.receiveOptionViewModel, this.hasWhiteBackground = false});
final ReceiveOptionViewModel receiveOptionViewModel;
final bool hasWhiteBackground;
@override
Widget build(BuildContext context) {
final arrowBottom =
Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6);
final textIconTheme = hasWhiteBackground
? Theme.of(context).accentTextTheme.headline2!.backgroundColor!
: Colors.white;
final arrowBottom = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: textIconTheme,
height: 6,
);
return TextButton(
onPressed: () => _showPicker(context),
@ -40,14 +48,14 @@ class PresentReceiveOptionPicker extends StatelessWidget {
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!),
color: textIconTheme),
),
Observer(
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.headline5!.color!)))
color: textIconTheme)))
],
),
SizedBox(width: 5),

View file

@ -32,7 +32,7 @@ class AnonpayCurrencyInputField extends StatelessWidget {
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
color: Theme.of(context).primaryTextTheme.bodyText1!.color!,
width: 1)),
),
child: Padding(

View file

@ -69,7 +69,7 @@ class AnonInvoiceForm extends StatelessWidget {
BaseTextFormField(
controller: nameController,
focusNode: _nameFocusNode,
borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
borderColor: Theme.of(context).primaryTextTheme.bodyText1!.color!,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).optional_name,
textInputAction: TextInputAction.next,
@ -88,7 +88,7 @@ class AnonInvoiceForm extends StatelessWidget {
controller: descriptionController,
focusNode: _descriptionFocusNode,
textInputAction: TextInputAction.next,
borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
borderColor: Theme.of(context).primaryTextTheme.bodyText1!.color!,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).optional_description,
placeholderTextStyle: TextStyle(
@ -104,7 +104,7 @@ class AnonInvoiceForm extends StatelessWidget {
controller: emailController,
textInputAction: TextInputAction.next,
focusNode: _emailFocusNode,
borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
borderColor: Theme.of(context).primaryTextTheme.bodyText1!.color!,
suffixIcon: SizedBox(width: 36),
keyboardType: TextInputType.emailAddress,
hintText: S.of(context).optional_email_hint,

View file

@ -1,4 +1,3 @@
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
@ -10,18 +9,20 @@ class CurrencyInputField extends StatelessWidget {
required this.onTapPicker,
required this.selectedCurrency,
this.focusNode,
required this.controller,
required this.controller, required this.isLight,
});
final Function() onTapPicker;
final Currency selectedCurrency;
final FocusNode? focusNode;
final TextEditingController controller;
final bool isLight;
@override
Widget build(BuildContext context) {
final arrowBottomPurple = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Colors.white,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
height: 8,
);
final _width = MediaQuery.of(context).size.width;
@ -38,14 +39,14 @@ class CurrencyInputField extends StatelessWidget {
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,8}'))],
hintText: '0.000',
placeholderTextStyle: TextStyle(
placeholderTextStyle: isLight ? null : TextStyle(
color: Theme.of(context).primaryTextTheme.headline5!.color!,
fontWeight: FontWeight.w600,
),
borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
textColor: Colors.white,
textColor: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
textStyle: TextStyle(
color: Colors.white,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
),
prefixIcon: Padding(
padding: EdgeInsets.only(
@ -68,7 +69,7 @@ class CurrencyInputField extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
),
),
if (selectedCurrency.tag != null)
@ -103,7 +104,8 @@ class CurrencyInputField extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 20,
color: Colors.white,
color:
Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
),
),
),

View file

@ -23,7 +23,7 @@ class QrImage extends StatelessWidget {
size: size,
foregroundColor: Colors.black,
backgroundColor: Colors.white,
padding: EdgeInsets.zero,
padding: const EdgeInsets.all(8.0),
);
}
}

View file

@ -119,6 +119,7 @@ class QRWidget extends StatelessWidget {
controller: amountController,
onTapPicker: () => _presentPicker(context),
selectedCurrency: addressListViewModel.selectedCurrency,
isLight: isLight,
),
),
),

View file

@ -1,83 +0,0 @@
import 'package:flutter/services.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class RestoreFromKeysFrom extends StatefulWidget {
@override
_RestoreFromKeysFromState createState() => _RestoreFromKeysFromState();
}
class _RestoreFromKeysFromState extends State<RestoreFromKeysFrom> {
final _formKey = GlobalKey<FormState>();
final _blockchainHeightKey = GlobalKey<BlockchainHeightState>();
final _nameController = TextEditingController();
final _addressController = TextEditingController();
final _viewKeyController = TextEditingController();
final _spendKeyController = TextEditingController();
final _wifController = TextEditingController();
@override
void initState() {
// _nameController.addListener(() =>
// widget.walletRestorationFromKeysVM.name = _nameController.text);
// _addressController.addListener(() =>
// widget.walletRestorationFromKeysVM.address = _addressController.text);
// _viewKeyController.addListener(() =>
// widget.walletRestorationFromKeysVM.viewKey = _viewKeyController.text);
// _spendKeyController.addListener(() =>
// widget.walletRestorationFromKeysVM.spendKey = _spendKeyController.text);
// _wifController.addListener(() =>
// widget.walletRestorationFromKeysVM.wif = _wifController.text);
super.initState();
}
@override
void dispose() {
_nameController.dispose();
_addressController.dispose();
_viewKeyController.dispose();
_spendKeyController.dispose();
_wifController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
BaseTextFormField(
controller: _addressController,
keyboardType: TextInputType.multiline,
maxLines: null,
hintText: S.of(context).restore_address,
),
Container(
padding: EdgeInsets.only(top: 20.0),
child: BaseTextFormField(
controller: _viewKeyController,
hintText: S.of(context).restore_view_key_private,
)),
Container(
padding: EdgeInsets.only(top: 20.0),
child: BaseTextFormField(
controller: _spendKeyController,
hintText: S.of(context).restore_spend_key_private,
)),
BlockchainHeightWidget(
key: _blockchainHeightKey,
onHeightChange: (height) {
// widget.walletRestorationFromKeysVM.height = height;
print(height);
}),
]),
),
);
}
}

View file

@ -548,6 +548,10 @@ class SendCardState extends State<SendCard>
}
void _setEffects(BuildContext context) {
if (_effectsInstalled) {
return;
}
if (output.address.isNotEmpty) {
addressController.text = output.address;
}
@ -558,10 +562,6 @@ class SendCardState extends State<SendCard>
noteController.text = output.note;
extractedAddressController.text = output.extractedAddress;
if (_effectsInstalled) {
return;
}
cryptoAmountController.addListener(() {
final amount = cryptoAmountController.text;

View file

@ -56,6 +56,18 @@ class PrivacyPage extends BasePage {
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setIsAppSecure(value);
}),
SettingsSwitcherCell(
title: S.current.disable_buy,
value: _privacySettingsViewModel.disableBuy,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setDisableBuy(value);
}),
SettingsSwitcherCell(
title: S.current.disable_sell,
value: _privacySettingsViewModel.disableSell,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setDisableSell(value);
}),
],
);
}),

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -48,6 +49,7 @@ class SecurityBackupPage extends BasePage {
),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
if (DeviceInfo.instance.isMobile)
Observer(builder: (_) {
return SettingsSwitcherCell(
title: S.current.settings_allow_biometrical_authentication,

View file

@ -1,8 +1,7 @@
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
class CheckBoxPicker extends StatefulWidget {
CheckBoxPicker({
@ -32,16 +31,8 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
@override
Widget build(BuildContext context) {
return AlertBackground(
child: Column(
return PickerWrapperWidget(
children: [
Expanded(
child: Stack(
alignment: Alignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (widget.title.isNotEmpty)
Container(
padding: EdgeInsets.symmetric(horizontal: 24),
@ -91,14 +82,6 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
),
),
],
),
SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
AlertCloseButton(),
],
),
),
],
),
);
}
@ -111,7 +94,10 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
shrinkWrap: true,
separatorBuilder: (context, index) => widget.isSeparated
? Divider(
color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
color: Theme.of(context)
.accentTextTheme
.headline6!
.backgroundColor!,
height: 1,
)
: const SizedBox(),

View file

@ -2,9 +2,8 @@
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cw_core/currency.dart';
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
class Picker<Item> extends StatefulWidget {
Picker({
@ -114,37 +113,16 @@ class _PickerState<Item> extends State<Picker<Item>> {
final mq = MediaQuery.of(context);
final bottom = mq.viewInsets.bottom;
final height = mq.size.height - bottom;
final screenCenter = height / 2;
double closeButtonBottom = 60;
double containerHeight = height * 0.65;
if (bottom > 0) {
// increase a bit or it gets too squished in the top
containerHeight = height * 0.75;
final containerCenter = containerHeight / 2;
final containerBottom = screenCenter - containerCenter;
final hasTitle = widget.title == null || widget.title!.isEmpty;
// position the close button right below the search container
closeButtonBottom = closeButtonBottom -
containerBottom +
(hasTitle ? padding : padding / 1.5);
}
return AlertBackground(
child: Column(
return PickerWrapperWidget(
hasTitle: widget.title?.isNotEmpty ?? false,
children: [
Expanded(
flex: 1,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (widget.title?.isNotEmpty ?? false)
Container(
padding: EdgeInsets.symmetric(horizontal: padding),
@ -165,10 +143,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container(
color: Theme.of(context)
.accentTextTheme
.headline6!
.color!,
color: Theme.of(context).accentTextTheme.headline6!.color!,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: containerHeight,
@ -189,26 +164,23 @@ class _PickerState<Item> extends State<Picker<Item>> {
.color!),
decoration: InputDecoration(
hintText: widget.hintText,
prefixIcon: Image.asset(
"assets/images/search_icon.png"),
prefixIcon:
Image.asset("assets/images/search_icon.png"),
filled: true,
fillColor: Theme.of(context)
.accentTextTheme
.headline3!
.color!,
alignLabelWithHint: false,
contentPadding:
const EdgeInsets.symmetric(
contentPadding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 16),
enabledBorder: OutlineInputBorder(
borderRadius:
BorderRadius.circular(14),
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: Colors.transparent,
)),
focusedBorder: OutlineInputBorder(
borderRadius:
BorderRadius.circular(14),
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: Colors.transparent,
)),
@ -246,8 +218,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
decoration:
TextDecoration.none,
decoration: TextDecoration.none,
color: Theme.of(context)
.primaryTextTheme
.headline6!
@ -266,19 +237,6 @@ class _PickerState<Item> extends State<Picker<Item>> {
),
)
],
),
SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
AlertCloseButton(bottom: closeButtonBottom),
],
),
),
// gives the extra spacing using MediaQuery.viewInsets.bottom
// to simulate a keyboard area
SizedBox(
height: bottom,
)
],
),
);
}

View file

@ -0,0 +1,61 @@
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
class PickerWrapperWidget extends StatelessWidget {
PickerWrapperWidget({required this.children, this.hasTitle = false});
final List<Widget> children;
final bool hasTitle;
@override
Widget build(BuildContext context) {
final double padding = 24;
final mq = MediaQuery.of(context);
final bottom = mq.viewInsets.bottom;
final height = mq.size.height - bottom;
final screenCenter = height / 2;
double closeButtonBottom = 60;
double containerHeight = height * 0.65;
if (bottom > 0) {
// increase a bit or it gets too squished in the top
containerHeight = height * 0.75;
final containerCenter = containerHeight / 2;
final containerBottom = screenCenter - containerCenter;
// position the close button right below the search container
closeButtonBottom = closeButtonBottom -
containerBottom + (!hasTitle ? padding : padding / 1.5);
}
return AlertBackground(
child: Column(
children: [
Expanded(
flex: 1,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: children,
),
SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
AlertCloseButton(bottom: closeButtonBottom),
],
),
),
// gives the extra spacing using MediaQuery.viewInsets.bottom
// to simulate a keyboard area
SizedBox(
height: bottom,
)
],
),
);
}
}

View file

@ -34,6 +34,8 @@ abstract class SettingsStoreBase with Store {
required BalanceDisplayMode initialBalanceDisplayMode,
required bool initialSaveRecipientAddress,
required bool initialAppSecure,
required bool initialDisableBuy,
required bool initialDisableSell,
required FiatApiMode initialFiatMode,
required bool initialAllowBiometricalAuthentication,
required ExchangeApiMode initialExchangeStatus,
@ -57,6 +59,8 @@ abstract class SettingsStoreBase with Store {
balanceDisplayMode = initialBalanceDisplayMode,
shouldSaveRecipientAddress = initialSaveRecipientAddress,
isAppSecure = initialAppSecure,
disableBuy = initialDisableBuy,
disableSell = initialDisableSell,
fiatApiMode = initialFiatMode,
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
@ -130,6 +134,16 @@ abstract class SettingsStoreBase with Store {
}
});
reaction(
(_) => disableBuy,
(bool disableBuy) => sharedPreferences.setBool(
PreferencesKey.disableBuyKey, disableBuy));
reaction(
(_) => disableSell,
(bool disableSell) => sharedPreferences.setBool(
PreferencesKey.disableSellKey, disableSell));
if (Platform.isAndroid) {
setIsAppSecureNative(isAppSecure);
}
@ -217,6 +231,12 @@ abstract class SettingsStoreBase with Store {
@observable
bool isAppSecure;
@observable
bool disableBuy;
@observable
bool disableSell;
@observable
bool allowBiometricalAuthentication;
@ -309,6 +329,10 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false;
final isAppSecure =
sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false;
final disableBuy =
sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false;
final disableSell =
sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
final currentFiatApiMode = FiatApiMode.deserialize(
raw: sharedPreferences
.getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw);
@ -388,6 +412,8 @@ abstract class SettingsStoreBase with Store {
initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialExchangeStatus: exchangeStatus,
@ -435,6 +461,10 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? shouldSaveRecipientAddress;
isAppSecure =
sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
disableBuy =
sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
disableSell =
sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
allowBiometricalAuthentication = sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
allowBiometricalAuthentication;

View file

@ -141,9 +141,9 @@ class ExceptionHandler {
"errno = 103", // SocketException: Software caused connection abort
"errno = 104", // SocketException: Connection reset by peer
"errno = 110", // SocketException: Connection timed out
"HttpException: Connection reset by peer",
"HttpException: Connection closed before full header was received",
"HandshakeException: Connection terminated during handshake",
"Connection reset by peer",
"Connection closed before full header was received",
"Connection terminated during handshake",
"PERMISSION_NOT_GRANTED",
];
@ -172,7 +172,7 @@ class ExceptionHandler {
}
await file.writeAsString(
"App Version: $currentVersion\n\nDevice Info $deviceInfo",
"App Version: $currentVersion\n\nDevice Info $deviceInfo\n\n",
mode: FileMode.append,
);
}
@ -193,6 +193,7 @@ class ExceptionHandler {
'systemVersion': data.systemVersion,
'model': data.model,
'localizedModel': data.localizedModel,
'isPhysicalDevice': data.isPhysicalDevice,
};
}

View file

@ -46,9 +46,7 @@ abstract class DashboardViewModelBase with Store {
required this.anonpayTransactionsStore})
: isOutdatedElectrumWallet = false,
hasSellAction = false,
isEnabledSellAction = false,
hasBuyAction = false,
isEnabledBuyAction = false,
hasExchangeAction = false,
isShowFirstYatIntroduction = false,
isShowSecondYatIntroduction = false,
@ -287,14 +285,19 @@ abstract class DashboardViewModelBase with Store {
@observable
bool hasExchangeAction;
@observable
bool isEnabledBuyAction;
@computed
bool get isEnabledBuyAction =>
!settingsStore.disableBuy && wallet.type != WalletType.haven;
@observable
bool hasBuyAction;
@observable
bool isEnabledSellAction;
@computed
bool get isEnabledSellAction =>
!settingsStore.disableSell &&
wallet.type != WalletType.haven &&
wallet.type != WalletType.monero &&
wallet.type != WalletType.litecoin;
@observable
bool hasSellAction;
@ -398,11 +401,7 @@ abstract class DashboardViewModelBase with Store {
void updateActions() {
hasExchangeAction = !isHaven;
isEnabledBuyAction = wallet.type != WalletType.haven;
hasBuyAction = !isHaven;
isEnabledSellAction = wallet.type != WalletType.haven
&& wallet.type != WalletType.monero
&& wallet.type != WalletType.litecoin;
hasSellAction = !isHaven;
}
}

View file

@ -123,8 +123,8 @@ abstract class ExchangeTradeViewModelBase with Store {
}
void _updateItems() {
final tagFrom = trade.from.tag != null ? '${trade.from.tag}' + ' ' : '';
final tagTo = trade.to.tag != null ? '${trade.to.tag}' + ' ' : '';
final tagFrom = tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
items.clear();
items.add(ExchangeTradeItem(
title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
@ -142,11 +142,11 @@ abstract class ExchangeTradeViewModelBase with Store {
items.addAll([
ExchangeTradeItem(title: S.current.amount, data: '${trade.amount}', isCopied: true),
ExchangeTradeItem(
title: S.current.send_to_this_address('${trade.from}', tagFrom) + ':',
title: S.current.send_to_this_address('${tradesStore.trade!.from}', tagFrom) + ':',
data: trade.inputAddress ?? '',
isCopied: true),
ExchangeTradeItem(
title: S.current.arrive_in_this_address('${trade.to}', tagTo) + ':',
title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':',
data: trade.payoutAddress ?? '',
isCopied: true),
]);

View file

@ -3,7 +3,6 @@ import 'dart:collection';
import 'dart:convert';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
@ -244,7 +243,7 @@ abstract class ExchangeViewModelBase with Store {
List<CryptoCurrency> depositCurrencies;
NumberFormat _cryptoNumberFormat;
final NumberFormat _cryptoNumberFormat;
final SettingsStore _settingsStore;
@ -388,6 +387,7 @@ abstract class ExchangeViewModelBase with Store {
double? lowestMin = double.maxFinite;
double? highestMax = 0.0;
try {
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
@ -410,6 +410,14 @@ abstract class ExchangeViewModelBase with Store {
continue;
}
}
} on ConcurrentModificationError {
/// if user changed the selected providers while fetching limits
/// then delay the fetching limits a bit and try again
///
/// this is because the limitation of collections that
/// you can't modify it while iterating through it
Future.delayed(Duration(milliseconds: 200), loadLimits);
}
if (lowestMin != double.maxFinite) {
limits = Limits(min: lowestMin, max: highestMax);
@ -534,7 +542,7 @@ abstract class ExchangeViewModelBase with Store {
///
/// this is because the limitation of the SplayTreeMap that
/// you can't modify it while iterating through it
Future.delayed(Duration(milliseconds: 500), createTrade);
Future.delayed(Duration(milliseconds: 200), createTrade);
}
}

View file

@ -24,6 +24,12 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get isAppSecure => _settingsStore.isAppSecure;
@computed
bool get disableBuy => _settingsStore.disableBuy;
@computed
bool get disableSell => _settingsStore.disableSell;
@action
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
@ -36,4 +42,10 @@ abstract class PrivacySettingsViewModelBase with Store {
@action
void setIsAppSecure(bool value) => _settingsStore.isAppSecure = value;
@action
void setDisableBuy(bool value) => _settingsStore.disableBuy = value;
@action
void setDisableSell(bool value) => _settingsStore.disableSell = value;
}

View file

@ -25,7 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
FlutterSecureStorageMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageMacosPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View file

@ -23,7 +23,7 @@ PODS:
- FlutterMacOS
- devicelocale (0.0.1):
- FlutterMacOS
- flutter_secure_storage_macos (3.3.1):
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- in_app_review (0.2.0):
@ -106,16 +106,16 @@ SPEC CHECKSUMS:
cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0
package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
platform_device_id: 3e414428f45df149bbbfb623e2c0ca27c545b763
platform_device_id_macos: f763bb55f088be804d61b96eb4710b8ab6598e94
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9

View file

@ -17,8 +17,8 @@ dependencies:
git:
url: https://github.com/cake-tech/flutter_secure_storage.git
path: flutter_secure_storage
ref: cake-6.0.0
version: 6.0.0
ref: cake-8.0.0
version: 8.0.0
# provider: ^6.0.3
rxdart: ^0.27.4
yaml: ^3.1.1
@ -81,6 +81,10 @@ dependencies:
path_provider_android: 2.0.24
shared_preferences_android: 2.0.17
url_launcher_android: 6.0.24
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v2
dev_dependencies:
flutter_test:

View file

@ -706,5 +706,7 @@
"error_text_input_below_minimum_limit":" المبلغ أقل من الحد الأدنى",
"error_text_input_above_maximum_limit":"المبلغ أكبر من الحد الأقصى",
"show_market_place": "إظهار السوق",
"prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة"
"prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة",
"disable_buy": "تعطيل إجراء الشراء",
"disable_sell": "قم بتعطيل إجراء البيع"
}

View file

@ -702,5 +702,7 @@
"error_text_input_below_minimum_limit" : "Сумата е по-малко от минималната",
"error_text_input_above_maximum_limit" : "Сумата надвишава максималната",
"show_market_place":"Покажи пазар",
"prevent_screenshots": "Предотвратете екранни снимки и запис на екрана"
"prevent_screenshots": "Предотвратете екранни снимки и запис на екрана",
"disable_buy": "Деактивирайте действието за покупка",
"disable_sell": "Деактивирайте действието за продажба"
}

View file

@ -702,5 +702,7 @@
"error_text_input_below_minimum_limit" : "Částka je menší než minimální hodnota",
"error_text_input_above_maximum_limit" : "Částka je větší než maximální hodnota",
"show_market_place": "Zobrazit trh",
"prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky"
"prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky",
"disable_buy": "Zakázat akci nákupu",
"disable_sell": "Zakázat akci prodeje"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Menge ist unter dem Minimum",
"error_text_input_above_maximum_limit" : "Menge ist über dem Maximum",
"show_market_place": "Marktplatz anzeigen",
"prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen"
"prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen",
"disable_buy": "Kaufaktion deaktivieren",
"disable_sell": "Verkaufsaktion deaktivieren"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Amount is less than the minimum",
"error_text_input_above_maximum_limit" : "Amount is more than the maximum",
"show_market_place" :"Show Marketplace",
"prevent_screenshots": "Prevent screenshots and screen recording"
"prevent_screenshots": "Prevent screenshots and screen recording",
"disable_buy": "Disable buy action",
"disable_sell": "Disable sell action"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "La cantidad es menos que mínima",
"error_text_input_above_maximum_limit" : "La cantidad es más que el máximo",
"show_market_place": "Mostrar mercado",
"prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla"
"prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla",
"disable_buy": "Desactivar acción de compra",
"disable_sell": "Desactivar acción de venta"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Le montant est inférieur au minimum",
"error_text_input_above_maximum_limit" : "Le montant est supérieur au maximum",
"show_market_place" :"Afficher la place de marché",
"prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran"
"prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran",
"disable_buy": "Désactiver l'action d'achat",
"disable_sell": "Désactiver l'action de vente"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "राशि न्यूनतम से कम है",
"error_text_input_above_maximum_limit" : "राशि अधिकतम से अधिक है",
"show_market_place":"बाज़ार दिखाएँ",
"prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें"
"prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें",
"disable_buy": "खरीद कार्रवाई अक्षम करें",
"disable_sell": "बेचने की कार्रवाई अक्षम करें"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Iznos je manji od minimalnog",
"error_text_input_above_maximum_limit" : "Iznos je veći od maskimalnog",
"show_market_place" : "Prikaži tržište",
"prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona"
"prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona",
"disable_buy": "Onemogući kupnju",
"disable_sell": "Onemogući akciju prodaje"
}

View file

@ -684,5 +684,7 @@
"error_text_input_below_minimum_limit" : "Jumlah kurang dari minimal",
"error_text_input_above_maximum_limit" : "Jumlah lebih dari maksimal",
"show_market_place": "Tampilkan Pasar",
"prevent_screenshots": "Cegah tangkapan layar dan perekaman layar"
"prevent_screenshots": "Cegah tangkapan layar dan perekaman layar",
"disable_buy": "Nonaktifkan tindakan beli",
"disable_sell": "Nonaktifkan aksi jual"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "L'ammontare è inferiore al minimo",
"error_text_input_above_maximum_limit" : "L'ammontare è superiore al massimo",
"show_market_place":"Mostra mercato",
"prevent_screenshots": "Impedisci screenshot e registrazione dello schermo"
"prevent_screenshots": "Impedisci screenshot e registrazione dello schermo",
"disable_buy": "Disabilita l'azione di acquisto",
"disable_sell": "Disabilita l'azione di vendita"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "金額は最小額より少ない",
"error_text_input_above_maximum_limit" : "金額は最大値を超えています",
"show_market_place":"マーケットプレイスを表示",
"prevent_screenshots": "スクリーンショットと画面録画を防止する"
"prevent_screenshots": "スクリーンショットと画面録画を防止する",
"disable_buy": "購入アクションを無効にする",
"disable_sell": "販売アクションを無効にする"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "금액이 최소보다 적습니다.",
"error_text_input_above_maximum_limit" : "금액이 최대 값보다 많습니다.",
"show_market_place":"마켓플레이스 표시",
"prevent_screenshots": "스크린샷 및 화면 녹화 방지"
"prevent_screenshots": "스크린샷 및 화면 녹화 방지",
"disable_buy": "구매 행동 비활성화",
"disable_sell": "판매 조치 비활성화"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "ပမာဏသည် အနိမ့်ဆုံးထက်နည်းသည်။",
"error_text_input_above_maximum_limit" : "ပမာဏသည် အများဆုံးထက် ပိုများသည်။",
"show_market_place":"စျေးကွက်ကိုပြသပါ။",
"prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။"
"prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။",
"disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
"disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Bedrag is minder dan minimaal",
"error_text_input_above_maximum_limit" : "Bedrag is meer dan maximaal",
"show_market_place":"Toon Marktplaats",
"prevent_screenshots": "Voorkom screenshots en schermopname"
"prevent_screenshots": "Voorkom screenshots en schermopname",
"disable_buy": "Koopactie uitschakelen",
"disable_sell": "Verkoopactie uitschakelen"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Kwota jest mniejsza niż minimalna",
"error_text_input_above_maximum_limit" : "Kwota jest większa niż maksymalna",
"show_market_place" : "Pokaż rynek",
"prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu"
"prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu",
"disable_buy": "Wyłącz akcję kupna",
"disable_sell": "Wyłącz akcję sprzedaży"
}

View file

@ -707,5 +707,7 @@
"error_text_input_below_minimum_limit" : "O valor é menor que o mínimo",
"error_text_input_above_maximum_limit" : "O valor é superior ao máximo",
"show_market_place":"Mostrar mercado",
"prevent_screenshots": "Evite capturas de tela e gravação de tela"
"prevent_screenshots": "Evite capturas de tela e gravação de tela",
"disable_buy": "Desativar ação de compra",
"disable_sell": "Desativar ação de venda"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Сумма меньше минимальной",
"error_text_input_above_maximum_limit" : "Сумма больше максимальной",
"show_market_place":"Показать торговую площадку",
"prevent_screenshots": "Предотвратить скриншоты и запись экрана"
"prevent_screenshots": "Предотвратить скриншоты и запись экрана",
"disable_buy": "Отключить действие покупки",
"disable_sell": "Отключить действие продажи"
}

View file

@ -706,5 +706,7 @@
"error_text_input_below_minimum_limit" : "จำนวนเงินน้อยกว่าขั้นต่ำ",
"error_text_input_above_maximum_limit" : "จำนวนเงินสูงกว่าค่าสูงสุด",
"show_market_place":"แสดงตลาดกลาง",
"prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ"
"prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ",
"disable_buy": "ปิดการใช้งานการซื้อ",
"disable_sell": "ปิดการใช้งานการขาย"
}

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Miktar minimumdan daha azdır",
"error_text_input_above_maximum_limit" : "Miktar maksimumdan daha fazla",
"show_market_place":"Pazar Yerini Göster",
"prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin"
"prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin",
"disable_buy": "Satın alma işlemini devre dışı bırak",
"disable_sell": "Satış işlemini devre dışı bırak"
}

View file

@ -707,5 +707,7 @@
"error_text_input_below_minimum_limit" : "Сума менша мінімальної",
"error_text_input_above_maximum_limit" : "Сума більше максимальної",
"show_market_place":"Відображати маркетплейс",
"prevent_screenshots": "Запобігати знімкам екрана та запису екрана"
"prevent_screenshots": "Запобігати знімкам екрана та запису екрана",
"disable_buy": "Вимкнути дію покупки",
"disable_sell": "Вимкнути дію продажу"
}

View file

@ -703,5 +703,7 @@
"error_text_input_below_minimum_limit" : "رقم کم از کم سے کم ہے۔",
"error_text_input_above_maximum_limit" : "رقم زیادہ سے زیادہ سے زیادہ ہے۔",
"show_market_place":"بازار دکھائیں۔",
"prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔"
"prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔",
"disable_buy": "خرید ایکشن کو غیر فعال کریں۔",
"disable_sell": "فروخت کی کارروائی کو غیر فعال کریں۔"
}

View file

@ -707,5 +707,7 @@
"error_text_input_below_minimum_limit" : "金额小于最小值",
"error_text_input_above_maximum_limit" : "金额大于最大值",
"show_market_place" :"显示市场",
"prevent_screenshots": "防止截屏和录屏"
"prevent_screenshots": "防止截屏和录屏",
"disable_buy": "禁用购买操作",
"disable_sell": "禁用卖出操作"
}

View file

@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.3.5"
MONERO_COM_BUILD_NUMBER=48
MONERO_COM_VERSION="1.3.6"
MONERO_COM_BUILD_NUMBER=49
MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.6.4"
CAKEWALLET_BUILD_NUMBER=158
CAKEWALLET_VERSION="4.6.5"
CAKEWALLET_BUILD_NUMBER=159
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"

View file

@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.3.5"
MONERO_COM_BUILD_NUMBER=46
MONERO_COM_VERSION="1.3.6"
MONERO_COM_BUILD_NUMBER=47
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.6.4"
CAKEWALLET_BUILD_NUMBER=153
CAKEWALLET_VERSION="4.6.5"
CAKEWALLET_BUILD_NUMBER=154
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven"

View file

@ -15,8 +15,8 @@ if [ -n "$1" ]; then
fi
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="1.0.3"
CAKEWALLET_BUILD_NUMBER=20
CAKEWALLET_VERSION="1.0.4"
CAKEWALLET_BUILD_NUMBER=22
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then