merged with 4.0.8

This commit is contained in:
Tanner Silva 2020-11-19 20:38:23 -06:00
commit 96c150bafa
117 changed files with 2660 additions and 1637 deletions

View file

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

View file

@ -41,7 +41,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.cakewallet.cake_wallet"
minSdkVersion 21
targetSdkVersion 28
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View file

@ -1,17 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cakewallet.cake_wallet">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<application
android:name="io.flutter.app.FlutterApplication"
android:label="Cake Wallet"
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
@ -21,17 +17,27 @@
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:screenOrientation="portrait">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<!-- <meta-data-->
<!-- android:name="io.flutter.embedding.android.NormalTheme"-->
<!-- android:resource="@style/NormalTheme"-->
<!-- />-->
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View file

@ -1,13 +1,15 @@
package com.cakewallet.cake_wallet;
import android.os.Bundle;
import io.flutter.app.FlutterFragmentActivity;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterFragmentActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterFragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

View file

@ -1,6 +1,13 @@
package com.cakewallet.cake_wallet
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine){
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

View file

@ -0,0 +1 @@
include ':app'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -4,7 +4,7 @@ import android.app.Activity
import android.os.AsyncTask
import android.os.Looper
import android.os.Handler
import android.os.Process
import android.os.Process
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
@ -22,10 +22,10 @@ class doAsync(val handler: () -> Unit) : AsyncTask<Void, Void, Void>() {
class CwMoneroPlugin: MethodCallHandler {
companion object {
val moneroApi = MoneroApi()
// val moneroApi = MoneroApi()
val main = Handler(Looper.getMainLooper());
init {
init {
System.loadLibrary("cw_monero")
}
@ -43,31 +43,31 @@ class CwMoneroPlugin: MethodCallHandler {
val password = call.argument("password") ?: ""
val useSSL = false
val isLightWallet = false
doAsync {
try {
moneroApi.setNodeAddressJNI(uri, login, password, useSSL, isLightWallet)
main.post({
result.success(true)
});
} catch(e: Throwable) {
main.post({
result.error("CONNECTION_ERROR", e.message, null)
});
}
}.execute()
// doAsync {
// try {
// moneroApi.setNodeAddressJNI(uri, login, password, useSSL, isLightWallet)
// main.post({
// result.success(true)
// });
// } catch(e: Throwable) {
// main.post({
// result.error("CONNECTION_ERROR", e.message, null)
// });
// }
// }.execute()
}
if (call.method == "startSync") {
doAsync {
moneroApi.startSyncJNI()
main.post({
result.success(true)
});
}.execute()
// doAsync {
// moneroApi.startSyncJNI()
// main.post({
// result.success(true)
// });
// }.execute()
}
if (call.method == "loadWallet") {
val path = call.argument("path") ?: ""
val password = call.argument("password") ?: ""
moneroApi.loadWalletJNI(path, password)
// moneroApi.loadWalletJNI(path, password)
result.success(true)
}
}

View file

@ -1,14 +0,0 @@
package com.cakewallet.monero
class MoneroApi {
external fun setNodeAddressJNI(uri: String, login: String, password: String, use_ssl: Boolean, is_light_wallet: Boolean)
external fun connectToNodeJNI()
external fun startSyncJNI()
external fun loadWalletJNI(path: String, password: String)
companion object {
init {
System.loadLibrary("cw_monero")
}
}
}

View file

@ -87,7 +87,7 @@ extern "C"
void updated()
{
m_need_to_refresh = true;
m_new_transaction = true;
}
void refreshed()
@ -351,13 +351,11 @@ extern "C"
uint64_t get_full_balance(uint32_t account_index)
{
// return 0;
return get_current_wallet()->balance(account_index);
}
uint64_t get_unlocked_balance(uint32_t account_index)
{
// return 0;
return get_current_wallet()->unlockedBalance(account_index);
}
@ -373,6 +371,7 @@ extern "C"
bool connect_to_node(char *error)
{
nice(19);
bool is_connected = get_current_wallet()->connectToDaemon();
if (!is_connected)
@ -385,6 +384,7 @@ extern "C"
bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error)
{
nice(19);
Monero::Wallet *wallet = get_current_wallet();
std::string _login = "";
@ -437,7 +437,7 @@ extern "C"
{
store_mutex.lock();
get_current_wallet()->store(std::string(path));
store_mutex.unlock();
store_mutex.unlock();
}
bool transaction_create(char *address, char *payment_id, char *amount,
@ -472,7 +472,9 @@ extern "C"
return false;
}
m_listener->m_new_transaction = true;
if (m_listener != nullptr) {
m_listener->m_new_transaction = true;
}
pendingTransaction = PendingTransactionRaw(transaction);
return true;
@ -485,7 +487,7 @@ extern "C"
if (!committed)
{
error = Utf8Box(strdup(transaction->transaction->errorString().c_str()));
} else {
} else if (m_listener != nullptr) {
m_listener->m_new_transaction = true;
}
@ -508,9 +510,8 @@ extern "C"
}
uint64_t height = m_listener->height();
uint64_t node_height = get_node_height_or_update(height);
if (height <= 1 || node_height <= 0) {
if (height <= 1) {
return 0;
}
@ -529,10 +530,8 @@ extern "C"
}
bool should_refresh = m_listener->isNeedToRefresh();
uint64_t node_height = get_node_height_or_update(m_last_known_wallet_height);
if (should_refresh || (node_height - m_last_known_wallet_height < MONERO_BLOCK_SIZE))
{
if (should_refresh) {
m_listener->resetNeedToRefresh();
}
@ -561,7 +560,7 @@ extern "C"
if (m_listener != nullptr)
{
// free(m_listener);
free(m_listener);
}
m_listener = new MoneroWalletListener();

View file

@ -26,7 +26,18 @@ final accountSetLabelNative = moneroApi
.lookup<NativeFunction<account_set_label>>('account_set_label_row')
.asFunction<AccountSetLabel>();
void refreshAccounts() => accountRefreshNative();
bool isUpdating = false;
void refreshAccounts() {
try {
isUpdating = true;
accountRefreshNative();
isUpdating = false;
} catch (e) {
isUpdating = false;
rethrow;
}
}
List<AccountRow> getAllAccount() {
final size = accountSizeNative();
@ -63,4 +74,4 @@ Future<void> addAccount({String label}) async => compute(_addAccount, label);
Future<void> setLabelForAccount({int accountIndex, String label}) async =>
compute(
_setLabelForAccount, {'accountIndex': accountIndex, 'label': label});
_setLabelForAccount, {'accountIndex': accountIndex, 'label': label});

View file

@ -26,8 +26,18 @@ final subaddrressSetLabelNative = moneroApi
.lookup<NativeFunction<subaddress_set_label>>('subaddress_set_label')
.asFunction<SubaddressSetLabel>();
void refreshSubaddresses({int accountIndex}) =>
bool isUpdating = false;
void refreshSubaddresses({@required int accountIndex}) {
try {
isUpdating = true;
subaddressRefreshNative(accountIndex);
isUpdating = false;
} catch (e) {
isUpdating = false;
rethrow;
}
}
List<SubaddressRow> getAllSubaddresses() {
final size = subaddressSizeNative();
@ -48,7 +58,7 @@ void addSubaddressSync({int accountIndex, String label}) {
void setLabelForSubaddressSync(
{int accountIndex, int addressIndex, String label}) {
final labelPointer = Utf8.toUtf8(label);
subaddrressSetLabelNative(accountIndex, addressIndex, labelPointer);
free(labelPointer);
}
@ -70,7 +80,8 @@ void _setLabelForSubaddress(Map<String, dynamic> args) {
}
Future addSubaddress({int accountIndex, String label}) async =>
compute<Map<String, Object>, void>(_addSubaddress, {'accountIndex': accountIndex, 'label': label});
compute<Map<String, Object>, void>(
_addSubaddress, {'accountIndex': accountIndex, 'label': label});
Future setLabelForSubaddress(
{int accountIndex, int addressIndex, String label}) =>

View file

@ -11,8 +11,6 @@ import 'package:flutter/services.dart';
int _boolToInt(bool value) => value ? 1 : 0;
final moneroAPIChannel = const MethodChannel('cw_monero');
final getFileNameNative = moneroApi
.lookup<NativeFunction<get_filename>>('get_filename')
.asFunction<GetFilename>();
@ -114,6 +112,8 @@ final rescanBlockchainAsyncNative = moneroApi
.lookup<NativeFunction<rescan_blockchain>>('rescan_blockchain')
.asFunction<RescanBlockchainAsync>();
bool isStoring = false;
int getSyncingHeight() => getSyncingHeightNative();
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
@ -209,20 +209,23 @@ String getSecretSpendKey() =>
String getPublicSpendKey() =>
convertUTF8ToString(pointer: getPublicSpendKeyNative());
class SyncListner {
SyncListner({this.onNewBlock, this.onNeedToRefresh, this.onNewTransaction});
class SyncListener {
SyncListener(this.onNewBlock, this.onNewTransaction) {
_cachedBlockchainHeight = 0;
_lastKnownBlockHeight = 0;
_initialSyncHeight = 0;
}
void Function(int, int, double) onNewBlock;
void Function() onNeedToRefresh;
void Function() onNewTransaction;
Timer _updateSyncInfoTimer;
int _cachedBlockchainHeight = 0;
int _lastKnownBlockHeight = 0;
int _initialSyncHeight = 0;
int _cachedBlockchainHeight;
int _lastKnownBlockHeight;
int _initialSyncHeight;
Future<int> getNodeHeightOrUpdate(int baseHeight) async {
if (_cachedBlockchainHeight < baseHeight) {
if (_cachedBlockchainHeight < baseHeight || _cachedBlockchainHeight == 0) {
_cachedBlockchainHeight = await getNodeHeight();
}
@ -234,54 +237,68 @@ class SyncListner {
_lastKnownBlockHeight = 0;
_initialSyncHeight = 0;
_updateSyncInfoTimer ??=
Timer.periodic(Duration(milliseconds: 200), (_) async {
final syncHeight = getSyncingHeight();
final needToRefresh = isNeededToRefresh();
final newTransactionExist = isNewTransactionExist();
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
if (_lastKnownBlockHeight != syncHeight && syncHeight != null) {
if (_initialSyncHeight <= 0) {
_initialSyncHeight = syncHeight;
}
_lastKnownBlockHeight = syncHeight;
final line = bchHeight - _initialSyncHeight;
final diff = line - (bchHeight - syncHeight);
final ptc = diff <= 0 ? 0.0 : diff / line;
final left = bchHeight - syncHeight;
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
onNewBlock(syncHeight, left, ptc);
}
if (newTransactionExist) {
Timer.periodic(Duration(milliseconds: 1200), (_) async {
if (isNewTransactionExist()) {
onNewTransaction?.call();
}
if (needToRefresh) {
onNeedToRefresh?.call();
var syncHeight = getSyncingHeight();
if (syncHeight <= 0) {
syncHeight = getCurrentHeight();
}
if (_initialSyncHeight <= 0) {
_initialSyncHeight = syncHeight;
}
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
if (_lastKnownBlockHeight == syncHeight || syncHeight == null) {
return;
}
_lastKnownBlockHeight = syncHeight;
final track = bchHeight - _initialSyncHeight;
final diff = track - (bchHeight - syncHeight);
final ptc = diff <= 0 ? 0.0 : diff / track;
final left = bchHeight - syncHeight;
if (syncHeight < 0 || left < 0) {
return;
}
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
onNewBlock?.call(syncHeight, left, ptc);
});
}
void stop() => _updateSyncInfoTimer?.cancel();
}
SyncListner setListeners(void Function(int, int, double) onNewBlock,
void Function() onNeedToRefresh, void Function() onNewTransaction) {
final listener = SyncListner(
onNewBlock: onNewBlock,
onNeedToRefresh: onNeedToRefresh,
onNewTransaction: onNewTransaction);
SyncListener setListeners(void Function(int, int, double) onNewBlock,
void Function() onNewTransaction) {
final listener = SyncListener(onNewBlock, onNewTransaction);
setListenerNative();
return listener;
}
void onStartup() => onStartupNative();
void _storeSync(Object _) => storeSync();
void _storeSync(Object _) {
if (isStoring) {
return;
}
try {
isStoring = true;
storeSync();
isStoring = false;
} catch (e) {
isStoring = false;
rethrow;
}
}
bool _setupNodeSync(Map args) {
final address = args['address'] as String;

View file

@ -2,25 +2,25 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
</dict>
</plist>

View file

@ -354,7 +354,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -362,6 +362,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -370,7 +371,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.0.0;
MARKETING_VERSION = 4.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -493,7 +494,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -501,6 +502,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -509,7 +511,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.0.0;
MARKETING_VERSION = 4.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -526,7 +528,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -534,6 +536,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -542,7 +545,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.0.0;
MARKETING_VERSION = 4.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View file

@ -60,7 +60,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.bnb:
return [42];
case CryptoCurrency.btc:
return [34, 42];
return [33, 34, 42];
case CryptoCurrency.dai:
return [42];
case CryptoCurrency.dash:

View file

@ -6,5 +6,5 @@ class NodeAddressValidator extends TextValidator {
: super(
errorMessage: S.current.error_text_node_address,
pattern:
'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$|^[0-9a-zA-Z.]+\$');
'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$|^[0-9a-zA-Z.\-]+\$');
}

View file

@ -6,7 +6,7 @@ class NodePortValidator extends TextValidator {
NodePortValidator()
: super(
errorMessage: S.current.error_text_node_port,
minLength: 1,
minLength: 0,
maxLength: 5,
pattern: '^[0-9]');
}

View file

@ -1,4 +1,5 @@
mixin PendingTransaction {
String get id;
String get amountFormatted;
String get feeFormatted;

View file

@ -2,10 +2,14 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/entities/biometric_auth.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/load_current_wallet.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@ -22,6 +26,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/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';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
@ -91,7 +96,8 @@ Future setup(
Box<Contact> contactSource,
Box<Trade> tradesSource,
Box<Template> templates,
Box<ExchangeTemplate> exchangeTemplates}) async {
Box<ExchangeTemplate> exchangeTemplates,
Box<TransactionDescription> transactionDescriptionBox}) async {
getIt.registerSingletonAsync<SharedPreferences>(
() => SharedPreferences.getInstance());
@ -156,7 +162,7 @@ Future setup(
});
getIt.registerFactory<WalletAddressListViewModel>(
() => WalletAddressListViewModel(wallet: getIt.get<AppStore>().wallet));
() => WalletAddressListViewModel(appStore: getIt.get<AppStore>()));
getIt.registerFactory(() => BalanceViewModel(
appStore: getIt.get<AppStore>(),
@ -195,6 +201,12 @@ Future setup(
}
authPageState.changeProcessText('Loading the wallet');
if (loginError != null) {
authPageState
.changeProcessText('ERROR: ${loginError.toString()}');
}
ReactionDisposer _reaction;
_reaction = reaction((_) => appStore.wallet, (Object _) {
_reaction?.reaction?.dispose();
@ -229,7 +241,8 @@ Future setup(
getIt.get<AppStore>().wallet,
getIt.get<AppStore>().settingsStore,
getIt.get<SendTemplateStore>(),
getIt.get<FiatConversionStore>()));
getIt.get<FiatConversionStore>(),
transactionDescriptionBox));
getIt.registerFactory(
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
@ -332,7 +345,8 @@ Future setup(
getIt.registerFactory(() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet,
trades: tradesSource,
tradesStore: getIt.get<TradesStore>()));
tradesStore: getIt.get<TradesStore>(),
sendViewModel: getIt.get<SendViewModel>()));
getIt.registerFactory(() => ExchangePage(getIt.get<ExchangeViewModel>()));
@ -385,4 +399,10 @@ Future setup(
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionInfo,
getIt.get<SettingsStore>().shouldSaveRecipientAddress,
transactionDescriptionBox));
}

View file

@ -1,7 +1,7 @@
String formatAmount(String amount) {
if (!amount.contains('.')) {
if ((!amount.contains('.'))&&(!amount.contains(','))) {
return amount + '.00';
} else if (amount.endsWith('.')) {
} else if ((amount.endsWith('.'))||(amount.endsWith(','))) {
return amount + '00';
}
return amount;

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/ios_legacy_helper.dart'
as ios_legacy_helper;
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
@ -98,7 +99,7 @@ Future<void> ios_migrate_user_defaults() async {
//assign the pin lenght
final pinLength = await ios_legacy_helper.getInt('pin-length');
await prefs.setInt('pin-length', pinLength);
await prefs.setInt(PreferencesKey.currentPinLength, pinLength);
//default value for display list key?
final walletName = await ios_legacy_helper.getString('current_wallet_name');
@ -390,32 +391,36 @@ Future<void> ios_migrate_trades_list(Box<Trade> tradeSource) async {
}
Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
final prefs = await SharedPreferences.getInstance();
try {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_address_book_completed') ?? false) {
return;
}
if (prefs.getBool('ios_migration_address_book_completed') ?? false) {
return;
}
final appDocDir = await getApplicationDocumentsDirectory();
final addressBookJSON = File('${appDocDir.path}/address_book.json');
final appDocDir = await getApplicationDocumentsDirectory();
final addressBookJSON = File('${appDocDir.path}/address_book.json');
if (!addressBookJSON.existsSync()) {
if (!addressBookJSON.existsSync()) {
await prefs.setBool('ios_migration_address_book_completed', true);
return;
}
final List<dynamic> addresses =
json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
final contacts = addresses.map((dynamic item) {
final _item = item as Map<String, dynamic>;
final type = _item["type"] as String;
final address = _item["address"] as String;
final name = _item["name"] as String;
return Contact(
address: address, name: name, type: CryptoCurrency.fromString(type));
});
await contactSource.addAll(contacts);
await prefs.setBool('ios_migration_address_book_completed', true);
return;
} catch(e) {
print(e.toString());
}
final List<dynamic> addresses =
json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
final contacts = addresses.map((dynamic item) {
final _item = item as Map<String, dynamic>;
final type = _item["type"] as String;
final address = _item["address"] as String;
final name = _item["name"] as String;
return Contact(
address: address, name: name, type: CryptoCurrency.fromString(type));
});
await contactSource.addAll(contacts);
await prefs.setBool('ios_migration_address_book_completed', true);
}

View file

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

View file

@ -18,7 +18,7 @@ class SyncingSyncStatus extends SyncStatus {
double progress() => ptc;
@override
String title() => S.current.sync_status_syncronizing;
String title() => S.current.Blocks_remaining('$blocksLeft');
@override
String toString() => '$blocksLeft';

View file

@ -18,19 +18,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
ChangeNowExchangeProvider()
: super(
pairList: CryptoCurrency.all
.map((i) {
return CryptoCurrency.all.map((k) {
if (i == CryptoCurrency.btc && k == CryptoCurrency.xmr) {
return ExchangePair(from: i, to: k, reverse: false);
}
if (i == CryptoCurrency.xmr && k == CryptoCurrency.btc) {
return null;
}
return ExchangePair(from: i, to: k, reverse: true);
}).where((c) => c != null);
})
.map((i) => CryptoCurrency.all
.map((k) => ExchangePair(from: i, to: k, reverse: true))
.where((c) => c != null))
.expand((i) => i)
.toList());
@ -43,10 +33,16 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
@override
String get title => 'ChangeNOW';
@override
bool get isAvailable => true;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.changeNow;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits({CryptoCurrency from, CryptoCurrency to}) async {
final symbol = from.toString() + '_' + to.toString();
@ -146,8 +142,10 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
@override
Future<double> calculateAmount(
{CryptoCurrency from, CryptoCurrency to, double amount,
bool isReceiveAmount}) async {
{CryptoCurrency from,
CryptoCurrency to,
double amount,
bool isReceiveAmount}) async {
final url = apiUri +
_exchangeAmountUriSufix +
amount.toString() +

View file

@ -12,6 +12,7 @@ abstract class ExchangeProvider {
String get title;
List<ExchangePair> pairList;
ExchangeProviderDescription description;
bool get isAvailable;
@override
String toString() => title;
@ -21,4 +22,5 @@ abstract class ExchangeProvider {
Future<Trade> findTradeById({@required String id});
Future<double> calculateAmount(
{CryptoCurrency from, CryptoCurrency to, double amount, bool isReceiveAmount});
Future<bool> checkIsAvailable();
}

View file

@ -60,10 +60,16 @@ class MorphTokenExchangeProvider extends ExchangeProvider {
@override
String get title => 'MorphToken';
@override
bool get isAvailable => true;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.morphToken;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits({CryptoCurrency from, CryptoCurrency to}) async {
final url = apiUri + _limitsURISuffix;

View file

@ -15,44 +15,47 @@ import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
class XMRTOExchangeProvider extends ExchangeProvider {
XMRTOExchangeProvider()
: super(pairList: [
: _isAvailable = false,
super(pairList: [
ExchangePair(
from: CryptoCurrency.xmr, to: CryptoCurrency.btc, reverse: false)
]);
static const userAgent = 'CakeWallet/XMR iOS';
static const originalApiUri = 'https://xmr.to/api/v3/xmr2btc';
static const proxyApiUri = 'https://xmrproxy.net/api/v3/xmr2btc';
static const _orderParameterUriSufix = '/order_parameter_query';
static const _orderStatusUriSufix = '/order_status_query/';
static const _orderCreateUriSufix = '/order_create/';
static String _apiUri = '';
static const _orderParameterUriSuffix = '/order_parameter_query';
static const _orderStatusUriSuffix = '/order_status_query/';
static const _orderCreateUriSuffix = '/order_create/';
static Future<String> getApiUri() async {
if (_apiUri != null && _apiUri.isNotEmpty) {
return _apiUri;
}
const url = originalApiUri + _orderParameterUriSufix;
static Future<bool> _checkIsAvailable() async {
const url = originalApiUri + _orderParameterUriSuffix;
final response =
await get(url, headers: {'Content-Type': 'application/json'});
_apiUri = response.statusCode == 403 ? proxyApiUri : originalApiUri;
return _apiUri;
return !(response.statusCode == 403);
}
@override
String get title => 'XMR.TO';
@override
bool get isAvailable => _isAvailable;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.xmrto;
double _rate = 0;
bool _isAvailable;
@override
Future<bool> checkIsAvailable() async {
_isAvailable = await _checkIsAvailable();
return isAvailable;
}
@override
Future<Limits> fetchLimits({CryptoCurrency from, CryptoCurrency to}) async {
final url = await getApiUri() + _orderParameterUriSufix;
final url = originalApiUri + _orderParameterUriSuffix;
final response = await get(url);
final correction = 0.001;
@ -67,9 +70,9 @@ class XMRTOExchangeProvider extends ExchangeProvider {
if (price > 0) {
try {
min = min/price + correction;
min = min / price + correction;
min = _limitsFormat(min);
max = max/price - correction;
max = max / price - correction;
max = _limitsFormat(max);
} catch (e) {
min = 0;
@ -86,11 +89,10 @@ class XMRTOExchangeProvider extends ExchangeProvider {
@override
Future<Trade> createTrade({TradeRequest request}) async {
final _request = request as XMRTOTradeRequest;
final url = await getApiUri() + _orderCreateUriSufix;
final url = originalApiUri + _orderCreateUriSuffix;
final body = {
'amount': _request.isBTCRequest
? _request.receiveAmount.replaceAll(',', '.')
: _request.amount.replaceAll(',', '.'),
'amount':
_request.isBTCRequest ? _request.receiveAmount : _request.amount,
'amount_currency': _request.isBTCRequest
? _request.to.toString()
: _request.from.toString(),
@ -129,7 +131,7 @@ class XMRTOExchangeProvider extends ExchangeProvider {
'Content-Type': 'application/json',
'User-Agent': userAgent
};
final url = await getApiUri() + _orderStatusUriSufix;
final url = originalApiUri + _orderStatusUriSuffix;
final body = {'uuid': id};
final response = await post(url, headers: headers, body: json.encode(body));
@ -170,8 +172,10 @@ class XMRTOExchangeProvider extends ExchangeProvider {
@override
Future<double> calculateAmount(
{CryptoCurrency from, CryptoCurrency to, double amount,
bool isReceiveAmount}) async {
{CryptoCurrency from,
CryptoCurrency to,
double amount,
bool isReceiveAmount}) async {
if (from != CryptoCurrency.xmr && to != CryptoCurrency.btc) {
return 0;
}
@ -181,7 +185,9 @@ class XMRTOExchangeProvider extends ExchangeProvider {
}
final double result = isReceiveAmount
? _rate == 0 ? 0 : amount / _rate
? _rate == 0
? 0
: amount / _rate
: _rate * amount;
return double.parse(result.toStringAsFixed(12));
@ -189,7 +195,7 @@ class XMRTOExchangeProvider extends ExchangeProvider {
Future<double> _fetchRates() async {
try {
final url = await getApiUri() + _orderParameterUriSufix;
final url = originalApiUri + _orderParameterUriSuffix;
final response =
await get(url, headers: {'Content-Type': 'application/json'});
final responseJSON = json.decode(response.body) as Map<String, dynamic>;

File diff suppressed because it is too large Load diff

View file

@ -29,46 +29,59 @@ import 'package:cake_wallet/src/screens/root/root.dart';
final navigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
try {
WidgetsFlutterBinding.ensureInitialized();
final appDir = await getApplicationDocumentsDirectory();
Hive.init(appDir.path);
Hive.registerAdapter(ContactAdapter());
Hive.registerAdapter(NodeAdapter());
Hive.registerAdapter(TransactionDescriptionAdapter());
Hive.registerAdapter(TradeAdapter());
Hive.registerAdapter(WalletInfoAdapter());
Hive.registerAdapter(WalletTypeAdapter());
Hive.registerAdapter(TemplateAdapter());
Hive.registerAdapter(ExchangeTemplateAdapter());
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: Trade.boxKey);
final contacts = await Hive.openBox<Contact>(Contact.boxName);
final nodes = await Hive.openBox<Node>(Node.boxName);
final transactionDescriptions = await Hive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades =
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
initialMigrationVersion: 4);
runApp(App());
final appDir = await getApplicationDocumentsDirectory();
Hive.init(appDir.path);
Hive.registerAdapter(ContactAdapter());
Hive.registerAdapter(NodeAdapter());
Hive.registerAdapter(TransactionDescriptionAdapter());
Hive.registerAdapter(TradeAdapter());
Hive.registerAdapter(WalletInfoAdapter());
Hive.registerAdapter(WalletTypeAdapter());
Hive.registerAdapter(TemplateAdapter());
Hive.registerAdapter(ExchangeTemplateAdapter());
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: Trade.boxKey);
final contacts = await Hive.openBox<Contact>(Contact.boxName);
final nodes = await Hive.openBox<Node>(Node.boxName);
final transactionDescriptions = await Hive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades =
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
initialMigrationVersion: 4);
runApp(App());
} catch (e) {
runApp(MaterialApp(
debugShowCheckedModeBanner: true,
home: Scaffold(
body: Container(
margin:
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Text(
'Error:\n${e.toString()}',
style: TextStyle(fontSize: 22),
)))));
}
}
Future<void> initialSetup(
@ -80,6 +93,7 @@ Future<void> initialSetup(
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
int initialMigrationVersion = 5}) async {
await defaultSettingsMigration(
version: initialMigrationVersion,
@ -94,7 +108,8 @@ Future<void> initialSetup(
contactSource: contactSource,
tradesSource: tradesSource,
templates: templates,
exchangeTemplates: exchangeTemplates);
exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions);
bootstrap(navigatorKey);
monero_wallet.onStartup();
}

View file

@ -67,38 +67,47 @@ final dates = {
"2019-5": 1824671,
"2019-6": 1847005,
"2019-7": 1868590,
"2019-8": 1888590,
"2019-9": 1898590,
"2019-8": 1890552,
"2019-9": 1912212,
"2019-10": 1932200,
"2019-11": 1957040,
"2019-12": 1978090,
"2020-1": 2001290,
"2020-2": 2022688,
"2020-3": 2043987,
"2020-4": 2066536,
"2020-5": 2090797,
"2020-6": 2111633,
"2020-7": 2131433,
"2020-8": 2153983,
"2020-9": 2176466,
"2020-10": 2198453,
"2020-11": 2220000
};
final heightCoefficient = 0.7;
int getHeigthByDate({DateTime date}) {
final raw = '${date.year}' + '-' + '${date.month}';
var endHeight = dates[raw] ?? 0;
int preLastYear = date.year;
int preLastMonth = date.month - 1;
final lastHeight = dates.values.last;
int startHeight;
int endHeight;
int height;
if (endHeight <= 0) {
if ((dates[raw] == null)||(dates[raw] == lastHeight)) {
startHeight = dates.values.toList()[dates.length - 2];
endHeight = dates.values.toList()[dates.length - 1];
final preLastDate =
dateFormat.parse(dates.keys.elementAt(dates.keys.length - 2));
preLastYear = preLastDate.year;
preLastMonth = preLastDate.month;
final heightPerDay = (endHeight - startHeight) / 31;
final daysHeight = (heightCoefficient * (date.day - 1) * heightPerDay).round();
height = endHeight + daysHeight;
} else {
preLastYear = date.year;
preLastMonth = date.month - 1;
startHeight = dates[raw];
final index = dates.values.toList().indexOf(startHeight);
endHeight = dates.values.toList()[index + 1];
final heightPerDay = ((endHeight - startHeight) / 31).round();
final daysHeight = (date.day - 1) * heightPerDay;
height = startHeight + daysHeight - heightPerDay;
}
if (preLastMonth <= 0) {
preLastMonth = 12;
preLastYear -= 1;
}
final startRaw = '$preLastYear' + '-' + '$preLastMonth';
final startHeight = dates[startRaw];
final diff = endHeight - startHeight;
final heightPerDay = diff / 30;
final daysHeight = date.day * heightPerDay.round();
final height = endHeight + daysHeight;
return height;
}

View file

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

View file

@ -7,9 +7,11 @@ final moneroAmountFormat = NumberFormat()
..maximumFractionDigits = moneroAmountLength
..minimumFractionDigits = 1;
String moneroAmountToString({int amount}) =>
moneroAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider));
String moneroAmountToString({int amount}) => moneroAmountFormat
.format(cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider));
double moneroAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider);
double moneroAmountToDouble({int amount}) =>
cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider);
int moneroParseAmount({String amount}) => moneroAmountFormat.parse(amount).toInt();
int moneroParseAmount({String amount}) =>
(double.parse(amount) * moneroAmountDivider).toInt();

View file

@ -0,0 +1,8 @@
class MoneroTransactionCreationException implements Exception {
MoneroTransactionCreationException(this.message);
final String message;
@override
String toString() => message;
}

View file

@ -8,7 +8,7 @@ import 'package:cw_monero/transaction_history.dart';
class MoneroTransactionInfo extends TransactionInfo {
MoneroTransactionInfo(this.id, this.height, this.direction, this.date,
this.isPending, this.amount, this.accountIndex);
this.isPending, this.amount, this.accountIndex, this.fee);
MoneroTransactionInfo.fromMap(Map map)
: id = (map['hash'] ?? '') as String,
@ -21,7 +21,8 @@ class MoneroTransactionInfo extends TransactionInfo {
isPending = parseBoolFromString(map['isPending'] as String),
amount = map['amount'] as int,
accountIndex = int.parse(map['accountIndex'] as String),
key = getTxKey((map['hash'] ?? '') as String);
key = getTxKey((map['hash'] ?? '') as String),
fee = map['fee'] as int ?? 0;
MoneroTransactionInfo.fromRow(TransactionInfoRow row)
: id = row.getHash(),
@ -32,7 +33,8 @@ class MoneroTransactionInfo extends TransactionInfo {
isPending = row.isPending != 0,
amount = row.getAmount(),
accountIndex = row.subaddrAccount,
key = getTxKey(row.getHash());
key = getTxKey(row.getHash()),
fee = row.fee;
final String id;
final int height;
@ -41,16 +43,22 @@ class MoneroTransactionInfo extends TransactionInfo {
final int accountIndex;
final bool isPending;
final int amount;
final int fee;
String recipientAddress;
String key;
String _fiatAmount;
@override
String amountFormatted() => '${formatAmount(moneroAmountToString(amount: amount))} XMR';
String amountFormatted() =>
'${formatAmount(moneroAmountToString(amount: amount))} XMR';
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
String feeFormatted() =>
'${formatAmount(moneroAmountToString(amount: fee))} XMR';
}

View file

@ -1,3 +1,7 @@
import 'dart:async';
import 'package:cake_wallet/monero/monero_amount_format.dart';
import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_monero/wallet.dart';
@ -19,7 +23,6 @@ import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/entities/transaction_priority.dart';
part 'monero_wallet.g.dart';
const moneroBlockSize = 1000;
@ -40,10 +43,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
subaddressList.update(accountIndex: account.id);
subaddress = subaddressList.subaddresses.first;
address = subaddress.address;
_lastAutosaveTimestamp = 0;
_isSavingAfterSync = false;
_isSavingAfterNewTransaction = false;
});
_cachedRefreshHeight = 0;
}
static const int _autoAfterSyncSaveInterval = 60000;
@override
final MoneroTransactionHistory transactionHistory;
@ -80,9 +87,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
final MoneroAccountList accountList;
String _filename;
SyncListner _listener;
SyncListener _listener;
ReactionDisposer _onAccountChangeReaction;
int _cachedRefreshHeight;
int _lastAutosaveTimestamp;
bool _isSavingAfterSync;
bool _isSavingAfterNewTransaction;
Future<void> init() async {
await accountList.update();
@ -92,10 +101,19 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
unlockedBalance:
monero_wallet.getFullBalance(accountIndex: account.id));
monero_wallet.getUnlockedBalance(accountIndex: account.id));
address = subaddress.address;
_setListeners();
await transactionHistory.update();
if (walletInfo.isRecovery) {
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
if (monero_wallet.getCurrentHeight() <= 1) {
monero_wallet.setRefreshFromBlockHeight(
height: walletInfo.restoreHeight);
}
}
}
void close() {
@ -103,6 +121,24 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
_onAccountChangeReaction?.reaction?.dispose();
}
bool validate() {
accountList.update();
final accountListLength = accountList.accounts?.length ?? 0;
if (accountListLength <= 0) {
return false;
}
subaddressList.update(accountIndex: accountList.accounts.first.id);
final subaddressListLength = subaddressList.subaddresses?.length ?? 0;
if (subaddressListLength <= 0) {
return false;
}
return true;
}
@override
Future<void> connectToNode({@required Node node}) async {
try {
@ -130,6 +166,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
try {
syncStatus = StartingSyncStatus();
monero_wallet.startRefresh();
_setListeners();
_listener?.start();
} catch (e) {
syncStatus = FailedSyncStatus();
print(e);
@ -140,6 +178,24 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
final amount = _credentials.amount != null
? moneroParseAmount(amount: _credentials.amount)
: null;
final unlockedBalance =
monero_wallet.getUnlockedBalance(accountIndex: account.id);
if ((amount != null && unlockedBalance < amount) ||
(amount == null && unlockedBalance <= 0)) {
final formattedBalance = moneroAmountToString(amount: unlockedBalance);
throw MoneroTransactionCreationException(
'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${_credentials.amount}.');
}
if (!(syncStatus is SyncedSyncStatus)) {
throw MoneroTransactionCreationException('The wallet is not synced.');
}
final pendingTransactionDescription =
await transaction_history.createTransaction(
address: _credentials.address,
@ -207,9 +263,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
void _setListeners() {
_listener?.stop();
_listener = monero_wallet.setListeners(
_onNewBlock, _onNeedToRefresh, _onNewTransaction);
_listener.start();
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
}
void _setInitialHeight() {
@ -247,8 +301,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
}
void _askForUpdateBalance() {
final fullBalance = _getFullBalance();
final unlockedBalance = _getUnlockedBalance();
final fullBalance = _getFullBalance();
if (balance.fullBalance != fullBalance ||
balance.unlockedBalance != unlockedBalance) {
@ -267,37 +321,68 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
int _getUnlockedBalance() =>
monero_wallet.getUnlockedBalance(accountIndex: account.id);
void _onNewBlock(int height, int blocksLeft, double ptc) =>
syncStatus = SyncingSyncStatus(blocksLeft, ptc);
Future _onNeedToRefresh() async {
if (syncStatus is FailedSyncStatus) {
Future<void> _afterSyncSave() async {
if (_isSavingAfterSync) {
return;
}
syncStatus = SyncedSyncStatus();
_isSavingAfterSync = true;
try {
final nowTimestamp = DateTime.now().millisecondsSinceEpoch;
final sum = _lastAutosaveTimestamp + _autoAfterSyncSaveInterval;
if (_lastAutosaveTimestamp > 0 && sum < nowTimestamp) {
return;
}
await save();
_lastAutosaveTimestamp = nowTimestamp + _autoAfterSyncSaveInterval;
} catch (e) {
print(e.toString());
}
_isSavingAfterSync = false;
}
Future<void> _afterNewTransactionSave() async {
if (_isSavingAfterNewTransaction) {
return;
}
_isSavingAfterNewTransaction = true;
try {
await save();
} catch (e) {
print(e.toString());
}
_isSavingAfterNewTransaction = false;
}
void _onNewBlock(int height, int blocksLeft, double ptc) async {
if (walletInfo.isRecovery) {
_askForUpdateTransactionHistory();
_askForUpdateBalance();
}
final currentHeight = getCurrentHeight();
final nodeHeight = monero_wallet.getNodeHeightSync();
if (blocksLeft < 100) {
_askForUpdateBalance();
syncStatus = SyncedSyncStatus();
await _afterSyncSave();
if (walletInfo.isRecovery &&
(nodeHeight - currentHeight < moneroBlockSize)) {
await setAsRecovered();
}
if (currentHeight - _cachedRefreshHeight > moneroBlockSize) {
_cachedRefreshHeight = currentHeight;
await save();
if (walletInfo.isRecovery) {
setAsRecovered();
}
} else {
syncStatus = SyncingSyncStatus(blocksLeft, ptc);
}
}
void _onNewTransaction() {
_askForUpdateBalance();
_askForUpdateTransactionHistory();
_askForUpdateBalance();
Timer(Duration(seconds: 1), () => _afterNewTransactionSave());
}
}

View file

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

View file

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

View file

@ -31,6 +31,6 @@ Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey) async {
startAuthenticationStateChange(authenticationStore, navigatorKey);
startCurrentWalletChangeReaction(
appStore, settingsStore, fiatConversionStore);
startCurrentFiatChangeReaction(appStore, settingsStore);
startCurrentFiatChangeReaction(appStore, settingsStore, fiatConversionStore);
startOnCurrentNodeChangeReaction(appStore);
}

View file

@ -7,13 +7,19 @@ import 'package:cake_wallet/store/authentication_store.dart';
ReactionDisposer _onAuthenticationStateChange;
dynamic loginError;
void startAuthenticationStateChange(AuthenticationStore authenticationStore,
@required GlobalKey<NavigatorState> navigatorKey) {
_onAuthenticationStateChange ??= autorun((_) async {
final state = authenticationStore.state;
if (state == AuthenticationState.installed) {
await loadCurrentWallet();
try {
await loadCurrentWallet();
} catch(e) {
loginError = e;
}
return;
}

View file

@ -1,18 +1,18 @@
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
ReactionDisposer _onCurrentFiatCurrencyChangeDisposer;
void startCurrentFiatChangeReaction(AppStore appStore, SettingsStore settingsStore) {
void startCurrentFiatChangeReaction(AppStore appStore, SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
_onCurrentFiatCurrencyChangeDisposer?.reaction?.dispose();
_onCurrentFiatCurrencyChangeDisposer = reaction(
(_) => settingsStore.fiatCurrency, (FiatCurrency fiatCurrency) async {
final cryptoCurrency = appStore.wallet.currency;
// final price = await fiatConvertationService.getPrice(
// crypto: cryptoCurrency, fiat: fiatCurrency);
//
// fiatConvertationStore.setPrice(price);
fiatConversionStore.price = await FiatConversionService.fetchPrice(
cryptoCurrency, fiatCurrency);
});
}

View file

@ -1,6 +1,8 @@
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
@ -49,6 +51,7 @@ import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
import 'package:hive/hive.dart';
Route<dynamic> createRoute(RouteSettings settings) {
switch (settings.name) {
@ -57,15 +60,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWalletFromWelcome:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) async {
builder: (_) => getIt.get<SetupPinCodePage>(param1:
(PinCodeState<PinCodeWidget> context, dynamic _) async {
try {
context.changeProcessText('Creating new wallet'); // FIXME: Unnamed constant
final newWalletVM = getIt.get<WalletNewVM>(param1: WalletType.monero);
await newWalletVM.create(options: 'English'); // FIXME: Unnamed constant
context.changeProcessText(
'Creating new wallet'); // FIXME: Unnamed constant
final newWalletVM =
getIt.get<WalletNewVM>(param1: WalletType.monero);
await newWalletVM.create(
options: 'English'); // FIXME: Unnamed constant
context.hideProgressText();
await Navigator.of(context.context).pushNamed(Routes.seed, arguments: true);
} catch(e) {
await Navigator.of(context.context)
.pushNamed(Routes.seed, arguments: true);
} catch (e) {
context.changeProcessText('Error: ${e.toString()}');
}
}),
@ -88,7 +95,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
Function(PinCodeState<PinCodeWidget>, String) callback;
if (settings.arguments is Function(PinCodeState<PinCodeWidget>, String)) {
callback = settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
callback =
settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
}
return CupertinoPageRoute<void>(
@ -137,8 +145,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreWalletOptionsFromWelcome:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (BuildContext context, dynamic _) =>
Navigator.pushNamed(context, Routes.restoreWallet)),
param1: (PinCodeState<PinCodeWidget> context, dynamic _) =>
Navigator.pushNamed(context.context, Routes.restoreWallet)),
fullscreenDialog: true);
case Routes.seed:
@ -194,8 +202,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.transactionDetails:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
TransactionDetailsPage(settings.arguments as TransactionInfo));
builder: (_) => getIt.get<TransactionDetailsPage>(
param1: settings.arguments as TransactionInfo));
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
@ -320,4 +328,4 @@ Route<dynamic> createRoute(RouteSettings settings) {
body: Center(
child: Text(S.current.router_no_route(settings.name)))));
}
}
}

View file

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

View file

@ -48,14 +48,15 @@ abstract class BasePage extends StatelessWidget {
return null;
}
final _backButton = Image.asset('assets/images/back_arrow.png',
color: titleColor ?? Theme.of(context).primaryTextTheme.title.color);
final _backButton = Icon(Icons.arrow_back_ios,
color: titleColor ?? Theme.of(context).primaryTextTheme.title.color,
size: 16,);
final _closeButton =
isDarkTheme ? _closeButtonImageDarkTheme : _closeButtonImage;
return SizedBox(
height: 37,
width: isModalBackButton ? 37 : 20,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: FlatButton(
@ -76,7 +77,7 @@ abstract class BasePage extends StatelessWidget {
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: titleColor ??
Theme.of(context).primaryTextTheme.title.color),
);

View file

@ -33,7 +33,7 @@ class FilterWidget extends StatelessWidget {
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins',
fontFamily: 'Lato',
decoration: TextDecoration.none,
),
),
@ -74,7 +74,7 @@ class FilterWidget extends StatelessWidget {
color: Theme.of(context).accentTextTheme.subhead.color,
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
fontFamily: 'Lato',
decoration: TextDecoration.none
),
),
@ -97,7 +97,7 @@ class FilterWidget extends StatelessWidget {
final item = section[index2];
final content = item.onChanged != null
? CheckboxWidget(
value: item.value,
value: item.value(),
caption: item.caption,
onChanged: item.onChanged
)
@ -127,7 +127,7 @@ class FilterWidget extends StatelessWidget {
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
fontSize: 18,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
decoration: TextDecoration.none
),

View file

@ -62,261 +62,269 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).backgroundColor,
child: Column(
children: <Widget>[
SizedBox(height: 10.0),
Expanded(
child: Stack(
return WillPopScope(
onWillPop: () async => false,
child: Container(
color: Theme.of(context).backgroundColor,
child: Column(
children: <Widget>[
SingleChildScrollView(
padding: EdgeInsets.only(left: 24.0, right: 24.0),
child: Column(
children: <Widget>[
Row(
SizedBox(height: 10.0),
Expanded(
child: Stack(
children: <Widget>[
SingleChildScrollView(
padding: EdgeInsets.only(left: 24.0, right: 24.0),
child: Column(
children: <Widget>[
Expanded(
child: Text(
'Terms and conditions',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
),
SizedBox(
height: 20.0,
),
Row(
children: <Widget>[
Expanded(
child: Text(
'Legal Disclaimer\nAnd\nTerms of Use',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
),
SizedBox(
height: 16.0,
),
Row(
children: <Widget>[
Expanded(
child: Text(
_fileText,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
))
],
),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
'Other Terms and Conditions',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () => launchUrl(xmrtoUrl),
child: Text(
xmrtoUrl,
textAlign: TextAlign.left,
style: TextStyle(
color: Palette.blueCraiola,
fontSize: 14.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.underline),
),
))
],
),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () => launchUrl(changenowUrl),
child: Text(
changenowUrl,
textAlign: TextAlign.left,
style: TextStyle(
color: Palette.blueCraiola,
fontSize: 14.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.underline),
),
))
],
),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () => launchUrl(morphUrl),
child: Text(
morphUrl,
textAlign: TextAlign.left,
style: TextStyle(
color: Palette.blueCraiola,
fontSize: 14.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.underline),
),
))
],
),
SizedBox(
height: 16.0,
)
],
),
),
Container(
alignment: Alignment.bottomCenter,
child: Container(
height: 12.0,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 0.7, sigmaY: 0.7),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context)
.backgroundColor
.withOpacity(0.0),
Theme.of(context).backgroundColor,
],
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
),
),
),
),
),
),
)
],
)),
if (!widget.isReadOnly) ...[
Row(
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(
left: 24.0, top: 10.0, right: 24.0, bottom: 10.0),
child: InkWell(
onTap: () {
setState(() {
_checked = !_checked;
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
Row(
children: <Widget>[
Container(
height: 24.0,
width: 24.0,
margin: EdgeInsets.only(
right: 10.0,
Expanded(
child: Text(
'Terms and conditions',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
width: 1.0),
borderRadius:
BorderRadius.all(Radius.circular(8.0)),
color: Theme.of(context).backgroundColor),
child: _checked
? Icon(
Icons.check,
color: Colors.blue,
size: 20.0,
)
: null,
),
Text(
'I agree to Terms of Use',
)
],
),
SizedBox(
height: 20.0,
),
Row(
children: <Widget>[
Expanded(
child: Text(
'Legal Disclaimer\nAnd\nTerms of Use',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
),
SizedBox(
height: 16.0,
),
Row(
children: <Widget>[
Expanded(
child: Text(
_fileText,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.0,
fontSize: 12.0,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
))
],
),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
'Other Terms and Conditions',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
),
)),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () => launchUrl(xmrtoUrl),
child: Text(
xmrtoUrl,
textAlign: TextAlign.left,
style: TextStyle(
color: Palette.blueCraiola,
fontSize: 14.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.underline),
),
))
],
),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () => launchUrl(changenowUrl),
child: Text(
changenowUrl,
textAlign: TextAlign.left,
style: TextStyle(
color: Palette.blueCraiola,
fontSize: 14.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.underline),
),
))
],
),
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () => launchUrl(morphUrl),
child: Text(
morphUrl,
textAlign: TextAlign.left,
style: TextStyle(
color: Palette.blueCraiola,
fontSize: 14.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.underline),
),
))
],
),
SizedBox(
height: 16.0,
)
],
),
),
Container(
alignment: Alignment.bottomCenter,
child: Container(
height: 12.0,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 0.7, sigmaY: 0.7),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context)
.backgroundColor
.withOpacity(0.0),
Theme.of(context).backgroundColor,
],
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
),
),
),
),
),
),
)
],
)),
if (!widget.isReadOnly) ...[
Row(
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(
left: 24.0, top: 10.0, right: 24.0, bottom: 10.0),
child: InkWell(
onTap: () {
setState(() {
_checked = !_checked;
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
height: 24.0,
width: 24.0,
margin: EdgeInsets.only(
right: 10.0,
),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
width: 1.0),
borderRadius: BorderRadius.all(
Radius.circular(8.0)),
color: Theme.of(context).backgroundColor),
child: _checked
? Icon(
Icons.check,
color: Colors.blue,
size: 20.0,
)
: null,
),
Text(
'I agree to Terms of Use',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
)
],
),
)),
),
],
),
Container(
padding:
EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0),
child: PrimaryButton(
onPressed: _checked
? () => Navigator.of(context)
.popAndPushNamed(Routes.welcome)
: null,
text: 'Accept',
color: Theme.of(context)
.accentTextTheme
.subtitle
.decorationColor,
textColor: Theme.of(context)
.accentTextTheme
.headline
.decorationColor),
),
],
),
Container(
padding: EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0),
child: PrimaryButton(
onPressed: _checked
? () =>
Navigator.of(context).popAndPushNamed(Routes.welcome)
: null,
text: 'Accept',
color: Colors.green,
textColor: Colors.white,
),
),
],
],
),
);
],
),
));
}
}

View file

@ -1,4 +1,5 @@
import 'dart:ui';
import 'package:cake_wallet/entities/sync_status.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@ -61,7 +62,10 @@ class ExchangePage extends BasePage {
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).reset, onPressed: () => exchangeViewModel.reset());
caption: S.of(context).reset, onPressed: () {
_formKey.currentState.reset();
exchangeViewModel.reset();
});
@override
Widget body(BuildContext context) {
@ -373,13 +377,12 @@ class ExchangePage extends BasePage {
child: Observer(builder: (_) {
final description =
exchangeViewModel.provider is XMRTOExchangeProvider
? exchangeViewModel.isReceiveAmountEntered
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate;
return Center(
child: Text(
description,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
@ -392,19 +395,30 @@ class ExchangePage extends BasePage {
}),
),
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).exchange,
onPressed: () {
if (_formKey.currentState.validate()) {
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

@ -205,13 +205,12 @@ class ExchangeTemplatePage extends BasePage {
child: Observer(builder: (_) {
final description =
exchangeViewModel.provider is XMRTOExchangeProvider
? exchangeViewModel.isReceiveAmountEntered
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate;
return Center(
child: Text(
description,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme

View file

@ -74,7 +74,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white
@ -95,6 +95,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
alignment: Alignment.center,
children: <Widget>[
GridView.count(
padding: EdgeInsets.all(0),
controller: controller,
crossAxisCount: crossAxisCount,
childAspectRatio: 1.25,
@ -136,7 +137,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
item.toString(),
style: TextStyle(
fontSize: 15,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
color: textColor

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
@ -14,8 +15,8 @@ class PresentProviderPicker extends StatelessWidget {
@override
Widget build(BuildContext context) {
final arrowBottom =
Image.asset('assets/images/arrow_bottom_purple_icon.png',
final arrowBottom = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Colors.white,
height: 6);
@ -50,8 +51,7 @@ class PresentProviderPicker extends StatelessWidget {
child: arrowBottom,
)
],
)
);
));
}
void _presentProviderPicker(BuildContext context) {
@ -64,7 +64,6 @@ class PresentProviderPicker extends StatelessWidget {
switch (provider.description) {
case ExchangeProviderDescription.xmrto:
images.add(Image.asset('assets/images/xmr_btc.png'));
description = S.of(context).picker_description;
break;
case ExchangeProviderDescription.changeNow:
images.add(Image.asset('assets/images/change_now.png'));
@ -76,14 +75,25 @@ class PresentProviderPicker extends StatelessWidget {
}
showPopUp<void>(
builder: (_) => Picker(
builder: (BuildContext popUpContext) => Picker(
items: items,
images: images,
selectedAtIndex: selectedItem,
title: S.of(context).change_exchange_provider,
description: description,
onItemSelected: (ExchangeProvider provider) =>
exchangeViewModel.changeProvider(provider: provider)),
onItemSelected: (ExchangeProvider provider) {
if (!provider.isAvailable) {
showPopUp<void>(
builder: (BuildContext popUpContext) => AlertWithOneAction(
alertTitle: 'Error',
alertContent: 'The exchange is blocked in your region.',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()),
context: context);
return;
}
exchangeViewModel.changeProvider(provider: provider);
}),
context: context);
}
}
}

View file

@ -1,31 +1,26 @@
import 'package:cake_wallet/palette.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/exchange_trade_item.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'dart:ui';
import 'package:mobx/mobx.dart';
import 'package:provider/provider.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
// import 'package:cake_wallet/src/stores/exchange_trade/exchange_trade_store.dart';
// import 'package:cake_wallet/src/stores/send/send_store.dart';
// import 'package:cake_wallet/src/stores/send/sending_state.dart';
// import 'package:cake_wallet/src/stores/wallet/wallet_store.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';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/widgets/timer_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
void showInformation(
ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) {
@ -110,7 +105,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
width: 16,
color: Theme.of(context).primaryTextTheme.overline.color);
//_setEffects(context);
_setEffects(context);
return Container(
child: ScrollableWithBottomSection(
@ -227,30 +222,25 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
);
}),
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
bottomSection: PrimaryButton(
onPressed: () {},
text: S.of(context).confirm,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white)
/*Observer(
builder: (_) => tradeStore.trade.from == CryptoCurrency.xmr &&
!(sendStore.state is TransactionCommitted)
bottomSection: Observer(builder: (_) {
final trade = widget.exchangeTradeViewModel.trade;
final sendingState =
widget.exchangeTradeViewModel.sendViewModel.state;
return trade.from == CryptoCurrency.xmr && !(sendingState is TransactionCommitted)
? LoadingPrimaryButton(
isDisabled: tradeStore.trade.inputAddress == null ||
tradeStore.trade.inputAddress.isEmpty,
isLoading: sendStore.state is CreatingTransaction ||
sendStore.state is TransactionCommitted,
onPressed: () => sendStore.createTransaction(
address: tradeStore.trade.inputAddress,
amount: tradeStore.trade.amount),
text: tradeStore.trade.provider ==
ExchangeProviderDescription.xmrto
? S.of(context).confirm
: S.of(context).send_xmr,
color: Colors.blue,
textColor: Colors.white)
: Offstage()),*/
),
isDisabled: trade.inputAddress == null ||
trade.inputAddress.isEmpty,
isLoading: sendingState is IsExecutingState,
onPressed: () =>
widget.exchangeTradeViewModel.confirmSending(),
text: trade.provider == ExchangeProviderDescription.xmrto
? S.of(context).confirm
: S.of(context).send_xmr,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white)
: Offstage();
})),
);
}
@ -259,10 +249,9 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
return;
}
/*final sendStore = Provider.of<SendStore>(context);
reaction((_) => sendStore.state, (SendingState state) {
if (state is SendingFailed) {
reaction((_) => this.widget.exchangeTradeViewModel.sendViewModel.state,
(ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
@ -271,30 +260,126 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()
);
buttonAction: () => Navigator.of(context).pop());
});
});
}
if (state is TransactionCreatedSuccessfully) {
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
return ConfirmSendingAlert(
alertTitle: S.of(context).confirm_sending,
alertContent: S.of(context).commit_transaction_amount_fee(
sendStore.pendingTransaction.amount,
sendStore.pendingTransaction.fee),
leftButtonText: S.of(context).ok,
rightButtonText: S.of(context).cancel,
actionLeftButton: () {
amount: S.of(context).send_amount,
amountValue: widget.exchangeTradeViewModel.sendViewModel
.pendingTransaction.amountFormatted,
fee: S.of(context).send_fee,
feeValue: widget.exchangeTradeViewModel.sendViewModel
.pendingTransaction.feeFormatted,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
Navigator.of(context).pop();
sendStore.commitTransaction();
widget.exchangeTradeViewModel.sendViewModel
.commitTransaction();
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return Observer(builder: (_) {
final state = widget
.exchangeTradeViewModel.sendViewModel.state;
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,
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 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,
),
),
),
),
),
)
],
);
});
});
},
actionRightButton: () => Navigator.of(context).pop()
);
actionLeftButton: () => Navigator.of(context).pop());
});
});
}
@ -308,12 +393,11 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
alertTitle: S.of(context).sending,
alertContent: S.of(context).transaction_sent,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()
);
buttonAction: () => Navigator.of(context).pop());
});
});
}
});*/
});
_effectsInstalled = true;
}

View file

@ -33,7 +33,7 @@ class InformationPage extends StatelessWidget {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
fontFamily: 'Poppins',
fontFamily: 'Lato',
decoration: TextDecoration.none,
color: Theme.of(context).accentTextTheme.caption.decorationColor
),

View file

@ -56,7 +56,7 @@ class MoneroAccountEditOrCreatePage extends BasePage {
text: moneroAccountCreationViewModel.isEdit
? S.of(context).rename
: S.of(context).add,
color: Colors.green,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isLoading: moneroAccountCreationViewModel.state
is IsExecutingState,

View file

@ -53,7 +53,7 @@ class MoneroAccountListPage extends StatelessWidget {
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins',
fontFamily: 'Lato',
decoration: TextDecoration.none,
color: Colors.white
),
@ -141,7 +141,7 @@ class MoneroAccountListPage extends StatelessWidget {
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: Colors.white,
decoration: TextDecoration.none,
),

View file

@ -32,7 +32,7 @@ class AccountTile extends StatelessWidget {
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: textColor,
decoration: TextDecoration.none,
),

View file

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

View file

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

View file

@ -21,23 +21,9 @@ class RescanPage extends BasePage {
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child:
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Column(
children: <Widget>[
BlockchainHeightWidget(key: _blockchainHeightWidgetKey),
Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text(
S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context).hintColor
),
),
)
],
),
BlockchainHeightWidget(key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) =>
_rescanViewModel.isButtonEnabled = value),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading:
@ -51,6 +37,7 @@ class RescanPage extends BasePage {
},
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isDisabled: !_rescanViewModel.isButtonEnabled,
))
]),
);

View file

@ -6,7 +6,10 @@ import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class WalletRestoreFromKeysFrom extends StatefulWidget {
WalletRestoreFromKeysFrom({Key key}) : super(key: key);
WalletRestoreFromKeysFrom({Key key, this.onHeightOrDateEntered})
: super(key: key);
final Function (bool) onHeightOrDateEntered;
@override
WalletRestoreFromKeysFromState createState() =>
@ -63,7 +66,9 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
hintText: S.of(context).restore_spend_key_private,
maxLines: null)),
BlockchainHeightWidget(
key: blockchainHeightKey, onHeightChange: (_) => null)
key: blockchainHeightKey,
onHeightChange: (_) => null,
onHeightOrDateEntered: widget.onHeightOrDateEntered)
]),
));
}

View file

@ -7,10 +7,12 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
class WalletRestoreFromSeedForm extends StatefulWidget {
WalletRestoreFromSeedForm({Key key, this.blockHeightFocusNode})
WalletRestoreFromSeedForm({Key key, this.blockHeightFocusNode,
this.onHeightOrDateEntered})
: super(key: key);
final FocusNode blockHeightFocusNode;
final Function (bool) onHeightOrDateEntered;
@override
WalletRestoreFromSeedFormState createState() =>
@ -46,6 +48,11 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
context: context,
builder: (BuildContext context) =>
SeedLanguagePicker(selected: language));
if (selected == null || selected.isEmpty) {
return;
}
_changeLanguage(selected);
},
child: Container(
@ -58,7 +65,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
readOnly: true)))),
BlockchainHeightWidget(
focusNode: widget.blockHeightFocusNode,
key: blockchainHeightKey)
key: blockchainHeightKey,
onHeightOrDateEntered: widget.onHeightOrDateEntered)
]));
}

View file

@ -28,8 +28,12 @@ class WalletRestorePage extends BasePage {
_pages.addAll([
WalletRestoreFromSeedForm(
key: walletRestoreFromSeedFormKey,
blockHeightFocusNode: _blockHeightFocusNode),
WalletRestoreFromKeysFrom(key: walletRestoreFromKeysFormKey)
blockHeightFocusNode: _blockHeightFocusNode,
onHeightOrDateEntered: (value)
=> walletRestoreViewModel.isButtonEnabled = value),
WalletRestoreFromKeysFrom(key: walletRestoreFromKeysFormKey,
onHeightOrDateEntered: (value)
=> walletRestoreViewModel.isButtonEnabled = value)
]);
}
@ -42,14 +46,11 @@ class WalletRestorePage extends BasePage {
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: titleColor ??
Theme.of(context).primaryTextTheme.title.color),
));
@override
String get title => S.current.restore_title_from_seed;
final WalletRestoreViewModel walletRestoreViewModel;
final PageController _controller;
final List<Widget> _pages;
@ -75,6 +76,21 @@ class WalletRestorePage extends BasePage {
}
});
reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode)
{
walletRestoreViewModel.isButtonEnabled = false;
walletRestoreFromSeedFormKey.currentState.blockchainHeightKey
.currentState.restoreHeightController.text = '';
walletRestoreFromSeedFormKey.currentState.blockchainHeightKey
.currentState.dateController.text = '';
walletRestoreFromKeysFormKey.currentState.blockchainHeightKey
.currentState.restoreHeightController.text = '';
walletRestoreFromKeysFormKey.currentState.blockchainHeightKey
.currentState.dateController.text = '';
});
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Expanded(
child: PageView.builder(
@ -105,10 +121,19 @@ class WalletRestorePage extends BasePage {
return LoadingPrimaryButton(
onPressed: () =>
walletRestoreViewModel.create(options: _credentials()),
text: S.of(context).seed_language_next,
color: Colors.green,
textColor: Colors.white,
isLoading: walletRestoreViewModel.state is IsExecutingState);
text: S.of(context).restore_recover,
color: Theme
.of(context)
.accentTextTheme
.subtitle
.decorationColor,
textColor: Theme
.of(context)
.accentTextTheme
.headline
.decorationColor,
isLoading: walletRestoreViewModel.state is IsExecutingState,
isDisabled: !walletRestoreViewModel.isButtonEnabled,);
},
))
]);

View file

@ -32,13 +32,11 @@ class WalletSeedPage extends BasePage {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
// FIXME: add translations
return AlertWithTwoActions(
alertTitle: 'Attention',
alertContent:
'The seed is the only way to recover your wallet. Have you written it down?',
leftButtonText: 'Go back',
rightButtonText: 'Yes, I have',
alertTitle: S.of(context).seed_alert_title,
alertContent: S.of(context).seed_alert_content,
leftButtonText: S.of(context).seed_alert_back,
rightButtonText: S.of(context).seed_alert_yes,
actionLeftButton: () => Navigator.of(context).pop(false),
actionRightButton: () => Navigator.of(context).pop(true));
}) ??

View file

@ -77,7 +77,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins',
fontFamily: 'Lato',
decoration: TextDecoration.none,
color: Colors.white
),
@ -92,6 +92,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
width: 300,
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: GridView.count(
padding: EdgeInsets.all(0),
shrinkWrap: true,
crossAxisCount: 3,
childAspectRatio: 1,
@ -171,7 +172,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontFamily: 'Lato',
decoration: TextDecoration.none,
color: textColor
),

View file

@ -70,7 +70,11 @@ class SendPage extends BasePage {
@override
Widget trailing(context) => TrailButton(
caption: S.of(context).clear, onPressed: () => sendViewModel.reset());
caption: S.of(context).clear,
onPressed: () {
_formKey.currentState.reset();
sendViewModel.reset();
});
@override
Widget body(BuildContext context) {
@ -163,72 +167,75 @@ class SendPage extends BasePage {
.decorationColor),
validator: sendViewModel.addressValidator,
),
Observer(builder: (_) => Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
focusNode: _cryptoAmountFocus,
controller: _cryptoAmountController,
keyboardType:
TextInputType.numberWithOptions(
signed: false, decimal: true),
prefixIcon: Padding(
padding: EdgeInsets.only(top: 9),
child: Text(
sendViewModel.currency.title + ':',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
)),
),
suffixIcon: 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: () =>
sendViewModel.setSendAll(),
child: Center(
child: Text(S.of(context).all,
textAlign: TextAlign.center,
Observer(
builder: (_) => Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
focusNode: _cryptoAmountFocus,
controller: _cryptoAmountController,
keyboardType:
TextInputType.numberWithOptions(
signed: false, decimal: true),
prefixIcon: Padding(
padding: EdgeInsets.only(top: 9),
child: Text(
sendViewModel.currency.title +
':',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.display1
.decorationColor)),
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
)),
),
),
),
hintText: '0.0000',
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
suffixIcon: 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: () =>
sendViewModel.setSendAll(),
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)),
),
),
),
hintText: '0.0000',
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator:
sendViewModel.sendAll
? sendViewModel.allAmountValidator
: sendViewModel.amountValidator))),
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: sendViewModel.sendAll
? sendViewModel.allAmountValidator
: sendViewModel
.amountValidator))),
Observer(
builder: (_) => Padding(
padding: EdgeInsets.only(top: 10),
@ -420,53 +427,60 @@ class SendPage extends BasePage {
)),
),
),
Observer(
builder: (_) {
final templates = sendViewModel.templates;
final itemCount = templates.length;
Observer(builder: (_) {
final templates = sendViewModel.templates;
final itemCount = templates.length;
return ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
return ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
return TemplateTile(
key: UniqueKey(),
to: template.name,
amount: template.amount,
from: template.cryptoCurrency,
onTap: () {
_addressController.text = template.address;
_cryptoAmountController.text = template.amount;
getOpenaliasRecord(context);
},
onRemove: () {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).template,
alertContent: S.of(context).confirm_delete_template,
rightButtonText: S.of(context).delete,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
Navigator.of(dialogContext).pop();
sendViewModel.removeTemplate(template: template);
sendViewModel.updateTemplate();
},
actionLeftButton: () => Navigator.of(dialogContext).pop()
);
}
);
},
);
}
);
}
)
return TemplateTile(
key: UniqueKey(),
to: template.name,
amount: template.amount,
from: template.cryptoCurrency,
onTap: () {
_addressController.text =
template.address;
_cryptoAmountController.text =
template.amount;
getOpenaliasRecord(context);
},
onRemove: () {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle:
S.of(context).template,
alertContent: S
.of(context)
.confirm_delete_template,
rightButtonText:
S.of(context).delete,
leftButtonText:
S.of(context).cancel,
actionRightButton: () {
Navigator.of(dialogContext)
.pop();
sendViewModel.removeTemplate(
template: template);
sendViewModel
.updateTemplate();
},
actionLeftButton: () =>
Navigator.of(dialogContext)
.pop());
});
},
);
});
})
],
),
),
@ -483,8 +497,14 @@ class SendPage extends BasePage {
}
},
text: S.of(context).send,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
color: Theme.of(context)
.accentTextTheme
.subtitle
.decorationColor,
textColor: Theme.of(context)
.accentTextTheme
.headline
.decorationColor,
isLoading: sendViewModel.state is IsExecutingState ||
sendViewModel.state is TransactionCommitting,
isDisabled:
@ -595,6 +615,10 @@ class SendPage extends BasePage {
return Observer(builder: (_) {
final state = sendViewModel.state;
if (state is FailureState) {
Navigator.of(context).pop();
}
if (state is TransactionCommitted) {
return Stack(
children: <Widget>[
@ -641,45 +665,49 @@ class SendPage extends BasePage {
);
}
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)),
if (state is TransactionCommitting) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
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,
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();
});
});
},

View file

@ -1,15 +1,17 @@
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class SendTemplatePage extends BasePage {
SendTemplatePage({@required this.sendViewModel});
@ -20,6 +22,8 @@ class SendTemplatePage extends BasePage {
final _fiatAmountController = TextEditingController();
final _nameController = TextEditingController();
final _formKey = GlobalKey<FormState>();
final FocusNode _cryptoAmountFocus = FocusNode();
final FocusNode _fiatAmountFocus = FocusNode();
bool _effectsInstalled = false;
@ -42,94 +46,153 @@ class SendTemplatePage extends BasePage {
Widget body(BuildContext context) {
_setEffects(context);
return Container(
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)
),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
child: Column(
children: <Widget>[
BaseTextFormField(
controller: _nameController,
hintText: S.of(context).send_name,
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: sendViewModel.templateValidator,
),
Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
controller: _addressController,
onURIScanned: (uri) {
var address = '';
var amount = '';
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: isDarkTheme
? Color.fromRGBO(48, 51, 60, 1.0)
: Color.fromRGBO(98, 98, 98, 1.0),
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
KeyboardActionsItem(
focusNode: _fiatAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: Container(
height: 0,
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
child: Column(
children: <Widget>[
BaseTextFormField(
controller: _nameController,
hintText: S.of(context).send_name,
borderColor:
Theme.of(context).primaryTextTheme.headline.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: sendViewModel.templateValidator,
),
Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
controller: _addressController,
onURIScanned: (uri) {
var address = '';
var amount = '';
if (uri != null) {
address = uri.path;
amount = uri.queryParameters['tx_amount'];
} else {
address = uri.toString();
}
if (uri != null) {
address = uri.path;
amount = uri.queryParameters['tx_amount'];
} else {
address = uri.toString();
}
_addressController.text = address;
_cryptoAmountController.text = amount;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor:
Theme.of(context).primaryTextTheme.display1.color,
borderColor:
Theme.of(context).primaryTextTheme.headline.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
_addressController.text = address;
_cryptoAmountController.text = amount;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor: Theme.of(context)
.primaryTextTheme
.display1
.color,
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor),
validator: sendViewModel.addressValidator,
),
),
Observer(builder: (_) {
return Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: _cryptoAmountController,
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor),
validator: sendViewModel.addressValidator,
),
),
Observer(builder: (_) {
return Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
focusNode: _cryptoAmountFocus,
controller: _cryptoAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
BlacklistingTextInputFormatter(
RegExp('[\\-|\\ ]'))
],
prefixIcon: Padding(
padding: EdgeInsets.only(top: 9),
child:
Text(sendViewModel.currency.title + ':',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
)),
),
hintText: '0.0000',
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: sendViewModel.amountValidator));
}),
Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
focusNode: _fiatAmountFocus,
controller: _fiatAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
@ -138,15 +201,14 @@ class SendTemplatePage extends BasePage {
],
prefixIcon: Padding(
padding: EdgeInsets.only(top: 9),
child:
Text(sendViewModel.currency.title + ':',
child: Text(sendViewModel.fiat.title + ':',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
)),
),
hintText: '0.0000',
hintText: '0.00',
borderColor: Theme.of(context)
.primaryTextTheme
.headline
@ -162,68 +224,34 @@ class SendTemplatePage extends BasePage {
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: sendViewModel.amountValidator));
}),
Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: _fiatAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
BlacklistingTextInputFormatter(
RegExp('[\\-|\\ ]'))
],
prefixIcon: Padding(
padding: EdgeInsets.only(top: 9),
child: Text(sendViewModel.fiat.title + ':',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
)),
),
hintText: '0.00',
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 14),
)),
],
),
)
]),
)),
],
),
)
]),
),
),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: PrimaryButton(
onPressed: () {
if (_formKey.currentState.validate()) {
sendViewModel.addTemplate(
name: _nameController.text,
address: _addressController.text,
cryptoCurrency: sendViewModel.currency.title,
amount: _cryptoAmountController.text);
sendViewModel.updateTemplate();
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Theme.of(context).accentTextTheme.subtitle.decorationColor,
textColor:
Theme.of(context).accentTextTheme.headline.decorationColor,
),
),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: PrimaryButton(
onPressed: () {
if (_formKey.currentState.validate()) {
sendViewModel.addTemplate(
name: _nameController.text,
address: _addressController.text,
cryptoCurrency: sendViewModel.currency.title,
amount: _cryptoAmountController.text);
sendViewModel.updateTemplate();
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white),
),
);
));
}
void _setEffects(BuildContext context) {

View file

@ -55,7 +55,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
@ -65,7 +65,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
@ -81,7 +81,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
@ -91,7 +91,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),

View file

@ -60,7 +60,7 @@ class AddressEditOrCreatePage extends BasePage {
text: addressEditOrCreateViewModel.isEdit
? S.of(context).rename
: S.of(context).new_subaddress_create,
color: Colors.green,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isLoading:
addressEditOrCreateViewModel.state is AddressIsSaving,

View file

@ -13,29 +13,29 @@ import 'package:cake_wallet/src/widgets/standart_list_row.dart';
class TradeDetailsPage extends BasePage {
TradeDetailsPage(this.trade) : _items = [] {
final dateFormat = DateFormatter.withCurrentLocal();
final items = [
_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(
_items.add(StandartListItem(
title: S.current.trade_details_provider,
value: trade.provider.toString()));
}
if (trade.createdAt != null) {
items.add(StandartListItem(
_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(
_items.add(StandartListItem(
title: S.current.trade_details_pair,
value: '${trade.from.toString()}${trade.to.toString()}'));
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -9,10 +10,15 @@ 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:url_launcher/url_launcher.dart';
import 'package:hive/hive.dart';
class TransactionDetailsPage extends BasePage {
TransactionDetailsPage(this.transactionInfo) : _items = [] {
TransactionDetailsPage(this.transactionInfo, bool showRecipientAddress,
Box<TransactionDescription> transactionDescriptionBox)
: _items = [] {
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
@ -31,16 +37,23 @@ class TransactionDetailsPage extends BasePage {
StandartListItem(
title: "View in Block Explorer",
value: "https://localmonero.co/blocks/search/${tx.id}"),
StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
];
// FIXME
// if (widget.settingsStore.shouldSaveRecipientAddress &&
// tx.recipientAddress != null) {
// items.add(StandartListItem(
// title: S.current.transaction_details_recipient_address,
// value: tx.recipientAddress));
// }
if (tx.key?.isNotEmpty) {
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));
}
}
if (tx.key?.isNotEmpty ?? null) {
// FIXME: add translation
items.add(StandartListItem(title: 'Transaction Key', value: tx.key));
}

View file

@ -87,11 +87,9 @@ class WalletListBodyState extends State<WalletListBody> {
final confirmed = await showPopUp<bool>(
context: context,
builder: (dialogContext) {
// FIXME:
return AlertWithTwoActions(
alertTitle: 'Change current wallet',
alertContent:
'Do you want to change current wallet to ${wallet.name} ?',
alertTitle: S.of(context).change_wallet_alert_title,
alertContent: S.of(context).change_wallet_alert_content(wallet.name),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () =>
@ -242,12 +240,12 @@ class WalletListBodyState extends State<WalletListBody> {
Future<void> _generateNewWallet() async {
try {
changeProcessText('Creating new wallet'); // FIXME: Unnamed constant
changeProcessText(S.of(context).creating_new_wallet);
await widget.walletListViewModel.walletNewVM.create(options: 'English'); // FIXME: Unnamed constant
hideProgressText();
await Navigator.of(context).pushNamed(Routes.seed, arguments: true);
} catch(e) {
changeProcessText('Error: ${e.toString()}');
changeProcessText(S.of(context).creating_new_wallet_error(e.toString()));
}
}

View file

@ -89,7 +89,7 @@ class WalletMenuAlert extends StatelessWidget {
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
fontSize: 18,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
decoration: TextDecoration.none
),

View file

@ -220,9 +220,9 @@ class AddressTextField extends StatelessWidget {
Future<void> _pasteAddress(BuildContext context) async {
String address;
await Clipboard.getData('text/plain').then((value) => address = value.text);
await Clipboard.getData('text/plain').then((value) => address = value?.text);
if (address.isNotEmpty) {
if (address?.isNotEmpty ?? false) {
controller.text = address;
}
}

View file

@ -1,161 +0,0 @@
import 'package:flutter/material.dart';
class Annotation extends Comparable<Annotation> {
Annotation({@required this.range, this.style});
final TextRange range;
final TextStyle style;
@override
int compareTo(Annotation other) => range.start.compareTo(other.range.start);
}
class TextAnnotation extends Comparable<TextAnnotation> {
TextAnnotation({@required this.text, this.style});
final TextStyle style;
final String text;
@override
int compareTo(TextAnnotation other) => text.compareTo(other.text);
}
class AnnotatedEditableText extends EditableText {
AnnotatedEditableText({
Key key,
FocusNode focusNode,
TextEditingController controller,
TextStyle style,
ValueChanged<String> onChanged,
ValueChanged<String> onSubmitted,
Color cursorColor,
Color selectionColor,
Color backgroundCursorColor,
TextSelectionControls selectionControls,
@required this.words,
}) : textAnnotations = words
.map((word) => TextAnnotation(
text: word,
style: TextStyle(
color: Colors.black,
backgroundColor: Colors.transparent,
fontWeight: FontWeight.normal,
fontSize: 16)))
.toList(),
super(
maxLines: null,
key: key,
focusNode: focusNode,
controller: controller,
cursorColor: cursorColor,
style: style,
keyboardType: TextInputType.text,
autocorrect: false,
autofocus: false,
selectionColor: selectionColor,
selectionControls: selectionControls,
backgroundCursorColor: backgroundCursorColor,
onChanged: onChanged,
onSubmitted: onSubmitted,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: true,
paste: true,
selectAll: true,
),
enableSuggestions: false,
enableInteractiveSelection: true,
showSelectionHandles: true,
showCursor: true,
) {
textAnnotations.add(TextAnnotation(
text: ' ', style: TextStyle(backgroundColor: Colors.transparent)));
}
final List<String> words;
final List<TextAnnotation> textAnnotations;
@override
AnnotatedEditableTextState createState() => AnnotatedEditableTextState();
}
class AnnotatedEditableTextState extends EditableTextState {
@override
AnnotatedEditableText get widget => super.widget as AnnotatedEditableText;
List<Annotation> getRanges() {
final source = widget.textAnnotations
.map((item) => range(item.text, textEditingValue.text)
.map((range) => Annotation(style: item.style, range: range)))
.expand((e) => e)
.toList();
final result = List<Annotation>();
final text = textEditingValue.text;
source.sort();
Annotation prev;
for (var item in source) {
if (prev == null) {
if (item.range.start > 0) {
result.add(Annotation(
range: TextRange(start: 0, end: item.range.start),
style: TextStyle(
color: Colors.black, backgroundColor: Colors.transparent)));
}
result.add(item);
prev = item;
continue;
} else {
if (prev.range.end > item.range.start) {
// throw StateError('Invalid (intersecting) ranges for annotated field');
} else if (prev.range.end < item.range.start) {
result.add(Annotation(
range: TextRange(start: prev.range.end, end: item.range.start),
style: TextStyle(
color: Colors.red, backgroundColor: Colors.transparent)));
}
result.add(item);
prev = item;
}
}
if (result.length > 0 && result.last.range.end < text.length) {
result.add(Annotation(
range: TextRange(start: result.last.range.end, end: text.length),
style: TextStyle( backgroundColor: Colors.transparent)));
}
return result;
}
List<TextRange> range(String pattern, String source) {
final result = List<TextRange>();
for (int index = source.indexOf(pattern);
index >= 0;
index = source.indexOf(pattern, index + 1)) {
final start = index;
final end = start + pattern.length;
result.add(TextRange(start: start, end: end));
}
return result;
}
@override
TextSpan buildTextSpan() {
final text = textEditingValue.text;
final ranges = getRanges();
if (ranges.isNotEmpty) {
return TextSpan(
style: widget.style,
children: ranges
.map((item) => TextSpan(
style: item.style, text: item.range.textInside(text)))
.toList());
}
return TextSpan(style: widget.style, text: text);
}
}

View file

@ -17,7 +17,7 @@ class BaseAlertDialog extends StatelessWidget {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
@ -32,7 +32,7 @@ class BaseAlertDialog extends StatelessWidget {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Poppins',
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
@ -59,7 +59,7 @@ class BaseAlertDialog extends StatelessWidget {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Colors.white,
decoration: TextDecoration.none,
@ -84,7 +84,7 @@ class BaseAlertDialog extends StatelessWidget {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Colors.white,
decoration: TextDecoration.none,

View file

@ -6,10 +6,12 @@ import 'package:cake_wallet/monero/get_height_by_date.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class BlockchainHeightWidget extends StatefulWidget {
BlockchainHeightWidget({GlobalKey key, this.onHeightChange, this.focusNode})
BlockchainHeightWidget({GlobalKey key, this.onHeightChange, this.focusNode,
this.onHeightOrDateEntered})
: super(key: key);
final Function(int) onHeightChange;
final Function(bool) onHeightOrDateEntered;
final FocusNode focusNode;
@override
@ -26,6 +28,13 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
@override
void initState() {
restoreHeightController.addListener(() {
if (restoreHeightController.text.isNotEmpty) {
widget.onHeightOrDateEntered?.call(true);
}
else {
widget.onHeightOrDateEntered?.call(false);
dateController.text = '';
}
try {
_changeHeight(restoreHeightController.text != null &&
restoreHeightController.text.isNotEmpty
@ -83,6 +92,18 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
))
],
),
Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text(
S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context).hintColor
),
),
)
],
);
}
@ -92,7 +113,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
final date = await getDate(
context: context,
initialDate: now.subtract(Duration(days: 1)),
firstDate: DateTime(2014, DateTime.april),
firstDate: DateTime(2014, DateTime.may),
lastDate: now);
if (date != null) {

View file

@ -68,7 +68,7 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
fontSize: 18,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
decoration: TextDecoration.none
),

View file

@ -70,7 +70,7 @@ class PickerState<Item> extends State<Picker> {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white
@ -90,6 +90,7 @@ class PickerState<Item> extends State<Picker> {
alignment: Alignment.center,
children: <Widget>[
ListView.separated(
padding: EdgeInsets.all(0),
controller: controller,
separatorBuilder: (context, index) => Divider(
color: Theme.of(context).accentTextTheme.title.backgroundColor,
@ -134,7 +135,7 @@ class PickerState<Item> extends State<Picker> {
item.toString(),
style: TextStyle(
fontSize: 18,
fontFamily: 'Poppins',
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: textColor,
decoration: TextDecoration.none,
@ -159,7 +160,7 @@ class PickerState<Item> extends State<Picker> {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
fontFamily: 'Lato',
decoration: TextDecoration.none,
color: Theme.of(context).primaryTextTheme
.title.color

View file

@ -1,5 +1,5 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/src/widgets/annotated_editable_text.dart';
import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@ -77,17 +77,28 @@ class SeedWidgetState extends State<SeedWidget> {
fontSize: 16.0, color: Theme.of(context).hintColor))),
Padding(
padding: EdgeInsets.only(right: 40, top: 10),
child: AnnotatedEditableText(
cursorColor: Colors.green,
backgroundCursorColor: Colors.blue,
style: TextStyle(
fontSize: 20,
color: Colors.red,
fontWeight: FontWeight.normal,
backgroundColor: Colors.transparent),
focusNode: focusNode,
controller: controller,
words: words)),
child: ValidatableAnnotatedEditableText(
cursorColor: Colors.blue,
backgroundCursorColor: Colors.blue,
validStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
backgroundColor: Colors.transparent,
fontWeight: FontWeight.normal,
fontSize: 16),
invalidStyle: TextStyle(
fontSize: 16,
color: Colors.red,
fontWeight: FontWeight.normal,
backgroundColor: Colors.transparent),
focusNode: focusNode,
controller: controller,
words: words,
textStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
backgroundColor: Colors.transparent,
fontWeight: FontWeight.normal,
fontSize: 16),
)),
Positioned(
top: 0,
right: 0,
@ -95,7 +106,7 @@ class SeedWidgetState extends State<SeedWidget> {
width: 34,
height: 34,
child: InkWell(
onTap: () async => _pasteAddress(),
onTap: () async => _pasteText(),
child: Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
@ -116,7 +127,7 @@ class SeedWidgetState extends State<SeedWidget> {
]));
}
Future<void> _pasteAddress() async {
Future<void> _pasteText() async {
final value = await Clipboard.getData('text/plain');
if (value?.text?.isNotEmpty ?? false) {

View file

@ -0,0 +1,170 @@
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:flutter/material.dart';
class Annotation extends Comparable<Annotation> {
Annotation({@required this.range, this.style});
final TextRange range;
final TextStyle style;
@override
int compareTo(Annotation other) => range.start.compareTo(other.range.start);
}
class TextAnnotation extends Comparable<TextAnnotation> {
TextAnnotation({@required this.text, this.style});
final TextStyle style;
final String text;
@override
int compareTo(TextAnnotation other) => text.compareTo(other.text);
}
class ValidatableAnnotatedEditableText extends EditableText {
ValidatableAnnotatedEditableText({
Key key,
FocusNode focusNode,
TextEditingController controller,
List<String> wordList,
ValueChanged<String> onChanged,
ValueChanged<String> onSubmitted,
Color cursorColor,
Color selectionColor,
Color backgroundCursorColor,
TextSelectionControls selectionControls,
this.validStyle,
this.invalidStyle,
TextStyle textStyle = const TextStyle(
color: Colors.black,
backgroundColor: Colors.transparent,
fontWeight: FontWeight.normal,
fontSize: 16),
@required this.words,
}) : super(
maxLines: null,
key: key,
focusNode: focusNode,
controller: controller,
cursorColor: cursorColor,
style: validStyle,
keyboardType: TextInputType.visiblePassword,
autocorrect: false,
autofocus: false,
selectionColor: selectionColor,
selectionControls: selectionControls,
backgroundCursorColor: backgroundCursorColor,
onChanged: onChanged,
onSubmitted: onSubmitted,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: true,
paste: true,
selectAll: true,
),
enableSuggestions: false,
enableInteractiveSelection: true,
showSelectionHandles: true,
showCursor: true);
final List<String> words;
final TextStyle validStyle;
final TextStyle invalidStyle;
@override
ValidatableAnnotatedEditableTextState createState() =>
ValidatableAnnotatedEditableTextState();
}
class ValidatableAnnotatedEditableTextState extends EditableTextState {
@override
ValidatableAnnotatedEditableText get widget =>
super.widget as ValidatableAnnotatedEditableText;
List<Annotation> getRanges() {
final result = List<Annotation>();
final text = textEditingValue.text;
final source = text
.split(' ')
.map((word) {
final ranges = range(word, text);
final isValid = validate(word);
return ranges.map((range) => Annotation(
style: isValid ? widget.validStyle : widget.invalidStyle,
range: range));
})
.expand((e) => e)
.toList();
source.sort();
Annotation prev;
for (var item in source) {
Annotation annotation;
if (prev == null) {
annotation = Annotation(
range: TextRange(start: 0, end: item.range.start),
style: TextStyle(
color: Colors.black, backgroundColor: Colors.transparent));
} else if (prev.range.end < item.range.start) {
annotation = Annotation(
range: TextRange(start: prev.range.end, end: item.range.start),
style: TextStyle(
color: Colors.red, backgroundColor: Colors.transparent));
}
if (annotation != null) {
result.add(annotation);
result.add(item);
prev = item;
}
}
if (result.length > 0 && result.last.range.end < text.length) {
result.add(Annotation(
range: TextRange(start: result.last.range.end, end: text.length),
style: TextStyle(backgroundColor: Colors.transparent)));
}
return result;
}
bool validate(String source) => widget.words.indexOf(source) >= 0;
List<TextRange> range(String pattern, String source) {
final result = List<TextRange>();
if (pattern.isEmpty || source.isEmpty) {
return result;
}
for (int index = source.indexOf(pattern);
index >= 0;
index = source.indexOf(pattern, index + 1)) {
final start = index;
final end = start + pattern.length;
result.add(TextRange(start: start, end: end));
}
return result;
}
@override
TextSpan buildTextSpan() {
final text = textEditingValue.text;
final ranges = getRanges().toSet();
if (ranges.isNotEmpty) {
return TextSpan(
style: widget.style,
children: ranges
.map((item) => TextSpan(
style: item.style, text: item.range.textInside(text)))
.toList());
}
return TextSpan(style: widget.style, text: text);
}
}

View file

@ -160,9 +160,12 @@ abstract class SettingsStoreBase with Store {
actionListDisplayMode.addAll(deserializeActionlistDisplayModes(
sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ??
defaultActionsMode));
final pinLength =
sharedPreferences.getInt(PreferencesKey.currentPinLength) ??
defaultPinLength;
var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength);
// If no value
if (pinLength == null || pinLength == 0) {
pinLength = defaultPinLength;
}
final savedLanguageCode =
sharedPreferences.getString(PreferencesKey.currentLanguageCode) ??
await LanguageService.localeDetection();

View file

@ -4,7 +4,7 @@ import 'palette.dart';
class Themes {
static final ThemeData lightTheme = ThemeData(
fontFamily: 'Poppins',
fontFamily: 'Lato',
brightness: Brightness.light,
backgroundColor: Colors.white,
accentColor: Palette.blueCraiola, // first gradient color
@ -179,7 +179,7 @@ class Themes {
static final ThemeData darkTheme = ThemeData(
fontFamily: 'Poppins',
fontFamily: 'Lato',
brightness: Brightness.dark,
backgroundColor: PaletteDark.backgroundColor,
accentColor: PaletteDark.backgroundColor, // first gradient color

View file

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

View file

@ -39,31 +39,31 @@ abstract class DashboardViewModelBase with Store {
filterItems = {
S.current.transactions: [
FilterItem(
value: transactionFilterStore.displayIncoming,
value: () => transactionFilterStore.displayIncoming,
caption: S.current.incoming,
onChanged: (value) => transactionFilterStore.toggleIncoming()),
FilterItem(
value: transactionFilterStore.displayOutgoing,
value: () => transactionFilterStore.displayOutgoing,
caption: S.current.outgoing,
onChanged: (value) => transactionFilterStore.toggleOutgoing()),
FilterItem(
value: false,
caption: S.current.transactions_by_date,
onChanged: null),
// FilterItem(
// value: () => false,
// caption: S.current.transactions_by_date,
// onChanged: null),
],
S.current.trades: [
FilterItem(
value: tradeFilterStore.displayXMRTO,
value: () => tradeFilterStore.displayXMRTO,
caption: 'XMR.TO',
onChanged: (value) => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.xmrto)),
FilterItem(
value: tradeFilterStore.displayChangeNow,
value: () => tradeFilterStore.displayChangeNow,
caption: 'Change.NOW',
onChanged: (value) => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
FilterItem(
value: tradeFilterStore.displayMorphToken,
value: () => tradeFilterStore.displayMorphToken,
caption: 'MorphToken',
onChanged: (value) => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.morphToken)),
@ -78,9 +78,8 @@ abstract class DashboardViewModelBase with Store {
.transactionHistory.transactions.values
.map((transaction) => TransactionListItem(
transaction: transaction,
price: price,
fiatCurrency: appStore.settingsStore.fiatCurrency,
displayMode: balanceDisplayMode)));
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
_reaction = reaction((_) => appStore.wallet, _onWalletChange);
// FIXME: fixme
@ -89,9 +88,8 @@ abstract class DashboardViewModelBase with Store {
transactions,
(TransactionInfo val) => TransactionListItem(
transaction: val,
price: price,
fiatCurrency: appStore.settingsStore.fiatCurrency,
displayMode: balanceDisplayMode));
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore));
final _wallet = wallet;
@ -185,8 +183,7 @@ abstract class DashboardViewModelBase with Store {
transactions.addAll(wallet.transactionHistory.transactions.values.map(
(transaction) => TransactionListItem(
transaction: transaction,
price: price,
fiatCurrency: appStore.settingsStore.fiatCurrency,
displayMode: balanceDisplayMode)));
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
}
}

View file

@ -1,7 +1,7 @@
class FilterItem {
FilterItem({this.value, this.caption, this.onChanged});
bool value;
bool Function() value;
String caption;
Function(bool) onChanged;
}

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
@ -8,15 +9,21 @@ import 'package:cake_wallet/monero/monero_transaction_info.dart';
import 'package:cake_wallet/monero/monero_amount_format.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
class TransactionListItem extends ActionListItem with Keyable {
TransactionListItem(
{this.transaction, this.price, this.fiatCurrency, this.displayMode});
{this.transaction, this.balanceViewModel, this.settingsStore});
final TransactionInfo transaction;
final double price;
final FiatCurrency fiatCurrency;
final BalanceDisplayMode displayMode;
final BalanceViewModel balanceViewModel;
final SettingsStore settingsStore;
double get price => balanceViewModel.price;
FiatCurrency get fiatCurrency => settingsStore.fiatCurrency;
BalanceDisplayMode get displayMode => settingsStore.balanceDisplayMode;
@override
dynamic get keyIndex => transaction.id;
@ -49,4 +56,4 @@ class TransactionListItem extends ActionListItem with Keyable {
@override
DateTime get date => transaction.date;
}
}

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dar
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
@ -18,7 +19,8 @@ class ExchangeTradeViewModel = ExchangeTradeViewModelBase
with _$ExchangeTradeViewModel;
abstract class ExchangeTradeViewModelBase with Store {
ExchangeTradeViewModelBase({this.wallet, this.trades, this.tradesStore}) {
ExchangeTradeViewModelBase(
{this.wallet, this.trades, this.tradesStore, this.sendViewModel}) {
trade = tradesStore.trade;
isSendable = trade.from == wallet.currency ||
@ -57,6 +59,7 @@ abstract class ExchangeTradeViewModelBase with Store {
final WalletBase wallet;
final Box<Trade> trades;
final TradesStore tradesStore;
final SendViewModel sendViewModel;
@observable
Trade trade;
@ -71,6 +74,17 @@ abstract class ExchangeTradeViewModelBase with Store {
Timer _timer;
@action
Future confirmSending() async {
if (!isSendable) {
return;
}
sendViewModel.address = trade.inputAddress;
sendViewModel.setCryptoAmount(trade.amount);
await sendViewModel.createTransaction();
}
@action
Future<void> _updateTrade() async {
try {

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/sync_status.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/limits.dart';
@ -27,10 +28,7 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase(
this.wallet,
this.trades,
this._exchangeTemplateStore,
this.tradesStore) {
this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore) {
providerList = [
XMRTOExchangeProvider(),
ChangeNowExchangeProvider(),
@ -42,12 +40,22 @@ abstract class ExchangeViewModelBase with Store {
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
depositAmount = '';
receiveAmount = '';
depositAddress = '';
receiveAddress = '';
depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
limitsState = LimitsInitialState();
tradeState = ExchangeTradeStateInitial();
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
provider = providersForCurrentPair().first;
final initialProvider = provider;
provider.checkIsAvailable().then((bool isAvailable) {
if (!isAvailable && provider == initialProvider) {
provider = providerList.firstWhere(
(provider) => provider is ChangeNowExchangeProvider,
orElse: () => providerList.last);
_onPairChange();
}
});
isReceiveAmountEntered = false;
loadLimits();
}
@ -100,6 +108,9 @@ abstract class ExchangeViewModelBase with Store {
NumberFormat _cryptoNumberFormat;
@computed
SyncStatus get status => wallet.syncStatus;
@computed
ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates;
@ -142,7 +153,9 @@ abstract class ExchangeViewModelBase with Store {
provider
.calculateAmount(
from: depositCurrency, to: receiveCurrency, amount: _amount,
from: depositCurrency,
to: receiveCurrency,
amount: _amount,
isReceiveAmount: true)
.then((amount) => _cryptoNumberFormat
.format(amount)
@ -164,7 +177,9 @@ abstract class ExchangeViewModelBase with Store {
final _amount = double.parse(amount.replaceAll(',', '.'));
provider
.calculateAmount(
from: depositCurrency, to: receiveCurrency, amount: _amount,
from: depositCurrency,
to: receiveCurrency,
amount: _amount,
isReceiveAmount: false)
.then((amount) => _cryptoNumberFormat
.format(amount)
@ -196,8 +211,8 @@ abstract class ExchangeViewModelBase with Store {
request = XMRTOTradeRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount,
receiveAmount: receiveAmount,
amount: depositAmount?.replaceAll(',', '.'),
receiveAmount: receiveAmount?.replaceAll(',', '.'),
address: receiveAddress,
refundAddress: depositAddress,
isBTCRequest: isReceiveAmountEntered);
@ -209,7 +224,7 @@ abstract class ExchangeViewModelBase with Store {
request = ChangeNowRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount,
amount: depositAmount?.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = depositAmount;
@ -220,13 +235,15 @@ abstract class ExchangeViewModelBase with Store {
request = MorphTokenRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount,
amount: depositAmount?.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = depositAmount;
currency = depositCurrency;
}
amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
if (double.parse(amount) < limits.min) {
tradeState = TradeIsCreatedFailure(
@ -271,19 +288,23 @@ abstract class ExchangeViewModelBase with Store {
void updateTemplate() => _exchangeTemplateStore.update();
void addTemplate({String amount, String depositCurrency, String receiveCurrency,
String provider, String depositAddress, String receiveAddress}) =>
_exchangeTemplateStore.addTemplate(
amount: amount,
depositCurrency: depositCurrency,
receiveCurrency: receiveCurrency,
provider: provider,
depositAddress: depositAddress,
receiveAddress: receiveAddress
);
void addTemplate(
{String amount,
String depositCurrency,
String receiveCurrency,
String provider,
String depositAddress,
String receiveAddress}) =>
_exchangeTemplateStore.addTemplate(
amount: amount,
depositCurrency: depositCurrency,
receiveCurrency: receiveCurrency,
provider: provider,
depositAddress: depositAddress,
receiveAddress: receiveAddress);
void removeTemplate({ExchangeTemplate template}) =>
_exchangeTemplateStore.remove(template: template);
_exchangeTemplateStore.remove(template: template);
List<ExchangeProvider> providersForCurrentPair() {
return _providersForPair(from: depositCurrency, to: receiveCurrency);
@ -316,9 +337,9 @@ abstract class ExchangeViewModelBase with Store {
}
}
depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
depositAmount = '';
receiveAmount = '';
loadLimits();
}

View file

@ -10,12 +10,16 @@ enum RescanWalletState { rescaning, none }
abstract class RescanViewModelBase with Store {
RescanViewModelBase(this._wallet) {
state = RescanWalletState.none;
isButtonEnabled = false;
}
@observable
RescanWalletState state;
final WalletBase _wallet;
@observable
bool isButtonEnabled;
@action
Future<void> rescanCurrentWallet({int restoreHeight}) async {
state = RescanWalletState.rescaning;

View file

@ -1,8 +1,10 @@
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/openalias_record.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/store/templates/send_template_store.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/template_validator.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
@ -30,9 +32,8 @@ part 'send_view_model.g.dart';
class SendViewModel = SendViewModelBase with _$SendViewModel;
abstract class SendViewModelBase with Store {
SendViewModelBase(
this._wallet, this._settingsStore, this._sendTemplateStore,
this._fiatConversationStore)
SendViewModelBase(this._wallet, this._settingsStore, this._sendTemplateStore,
this._fiatConversationStore, this.transactionDescriptionBox)
: state = InitialExecutionState(),
_cryptoNumberFormat = NumberFormat(),
sendAll = false {
@ -101,12 +102,14 @@ abstract class SendViewModelBase with Store {
final SendTemplateStore _sendTemplateStore;
final FiatConversionStore _fiatConversationStore;
final NumberFormat _cryptoNumberFormat;
final Box<TransactionDescription> transactionDescriptionBox;
@action
void setSendAll() => sendAll = true;
@action
void reset() {
sendAll = false;
cryptoAmount = '';
fiatAmount = '';
address = '';
@ -128,6 +131,13 @@ abstract class SendViewModelBase with Store {
try {
state = TransactionCommitting();
await pendingTransaction.commit();
if (_settingsStore.shouldSaveRecipientAddress &&
(pendingTransaction.id?.isNotEmpty ?? false)) {
await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction.id, recipientAddress: address));
}
state = TransactionCommitted();
} catch (e) {
state = FailureState(e.toString());
@ -155,8 +165,8 @@ abstract class SendViewModelBase with Store {
_settingsStore.transactionPriority = priority;
Future<OpenaliasRecord> decodeOpenaliasRecord(String name) async {
final record = await OpenaliasRecord
.fetchAddressAndName(OpenaliasRecord.formatDomainName(name));
final record = await OpenaliasRecord.fetchAddressAndName(
OpenaliasRecord.formatDomainName(name));
return record.name != name ? record : null;
}
@ -231,13 +241,16 @@ abstract class SendViewModelBase with Store {
void updateTemplate() => _sendTemplateStore.update();
void addTemplate({String name, String address, String cryptoCurrency,
String amount}) => _sendTemplateStore
.addTemplate(
name: name,
address: address,
cryptoCurrency: cryptoCurrency,
amount: amount);
void addTemplate(
{String name,
String address,
String cryptoCurrency,
String amount}) =>
_sendTemplateStore.addTemplate(
name: name,
address: address,
cryptoCurrency: cryptoCurrency,
amount: amount);
void removeTemplate({Template template}) =>
_sendTemplateStore.remove(template: template);

View file

@ -120,13 +120,13 @@ abstract class SettingsViewModelBase with Store {
LinkListItem(
title: 'Telegram',
icon: 'assets/images/Telegram.png',
linkTitle: 'Cake_Wallet',
linkTitle: '@cakewallet_bot',
link: 'https:t.me/cakewallet_bot'),
LinkListItem(
title: 'Twitter',
icon: 'assets/images/Twitter.png',
linkTitle: '@CakeWalletXMR',
link: 'https:twitter.com/CakewalletXMR'),
linkTitle: '@cakewallet',
link: 'https://twitter.com/cakewallet'),
LinkListItem(
title: 'ChangeNow',
icon: 'assets/images/change_now.png',

Some files were not shown because too many files have changed in this diff Show more