mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-12 09:32:33 +00:00
merged with 4.0.8
This commit is contained in:
commit
96c150bafa
117 changed files with 2660 additions and 1637 deletions
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
1
android/settings_aar.gradle
Normal file
1
android/settings_aar.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
include ':app'
|
Binary file not shown.
BIN
assets/fonts/Lato-Medium.ttf
Normal file
BIN
assets/fonts/Lato-Medium.ttf
Normal file
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.
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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});
|
|
@ -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}) =>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.\-]+\$');
|
||||
}
|
||||
|
|
|
@ -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]');
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
mixin PendingTransaction {
|
||||
String get id;
|
||||
String get amountFormatted;
|
||||
String get feeFormatted;
|
||||
|
||||
|
|
28
lib/di.dart
28
lib/di.dart
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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() +
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ abstract class MoneroAccountListBase with Store {
|
|||
bool _isRefreshing;
|
||||
bool _isUpdating;
|
||||
|
||||
Future update() async {
|
||||
void update() async {
|
||||
if (_isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
8
lib/monero/monero_transaction_creation_exception.dart
Normal file
8
lib/monero/monero_transaction_creation_exception.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
class MoneroTransactionCreationException implements Exception {
|
||||
MoneroTransactionCreationException(this.message);
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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)))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
]),
|
||||
)),
|
||||
));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -32,7 +32,7 @@ class AccountTile extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Poppins',
|
||||
fontFamily: 'Lato',
|
||||
color: textColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
})),
|
||||
],
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
]),
|
||||
);
|
||||
|
|
|
@ -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)
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
]));
|
||||
}
|
||||
|
||||
|
|
|
@ -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,);
|
||||
},
|
||||
))
|
||||
]);
|
||||
|
|
|
@ -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));
|
||||
}) ??
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()}'));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
170
lib/src/widgets/validable_annotated_editable_text.dart
Normal file
170
lib/src/widgets/validable_annotated_editable_text.dart
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -110,6 +110,8 @@ abstract class AuthViewModelBase with Store {
|
|||
|
||||
if (isAuthenticated) {
|
||||
state = ExecutedSuccessfullyState();
|
||||
} else {
|
||||
state = FailureState('Failure biometric authentication');
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class FilterItem {
|
||||
FilterItem({this.value, this.caption, this.onChanged});
|
||||
|
||||
bool value;
|
||||
bool Function() value;
|
||||
String caption;
|
||||
Function(bool) onChanged;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue