diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml new file mode 100644 index 000000000..190b891e1 --- /dev/null +++ b/.github/workflows/pr_test_build.yml @@ -0,0 +1,129 @@ +name: PR Test Build + +on: + pull_request: + branches: [ main ] + +jobs: + test: + + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '8.x' + + - name: Flutter action + uses: subosito/flutter-action@v1 + with: + flutter-version: '3.3.x' + channel: stable + + - name: Install package dependencies + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + + - name: Execute Build and Setup Commands + run: | + sudo mkdir -p /opt/android + sudo chown $USER /opt/android + cd /opt/android + git clone https://github.com/cake-tech/cake_wallet.git --branch $GITHUB_HEAD_REF + cd cake_wallet/scripts/android/ + ./install_ndk.sh + source ./app_env.sh cakewallet + ./app_config.sh + ./build_all.sh + ./copy_monero_deps.sh + + - name: Install Flutter dependencies + run: | + cd /opt/android/cake_wallet + flutter pub get + + - name: Generate KeyStore + run: | + cd /opt/android/cake_wallet/android/app + keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass ${{ secrets.STORE_PASS }} -keypass ${{ secrets.KEY_PASS }} + + - name: Generate key properties + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=${{ secrets.STORE_PASS }} keyPassword=${{ secrets.KEY_PASS }} + + - name: Generate localization + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_localization.dart + + - name: Build generated code + run: | + cd /opt/android/cake_wallet + cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + flutter packages pub run build_runner build --delete-conflicting-outputs + + - name: Add secrets + run: | + cd /opt/android/cake_wallet + touch lib/.secrets.g.dart + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart + echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const sideShiftApiKey = '${{ secrets.SIDE_SHIFT_API_KEY }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart + echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart + echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart + + - name: Rename app + run: sed -i -e "s/\${APP_NAME}/$GITHUB_HEAD_REF/g" /opt/android/cake_wallet/android/app/src/main/AndroidManifest.xml + + - name: Build + run: | + cd /opt/android/cake_wallet + flutter build apk --release + +# - name: Push to App Center +# run: | +# echo 'Installing App Center CLI tools' +# npm install -g appcenter-cli +# echo "Publishing test to App Center" +# appcenter distribute release \ +# --group "Testers" \ +# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ +# --release-notes ${GITHUB_HEAD_REF} \ +# --app Cake-Labs/Cake-Wallet \ +# --token ${{ secrets.APP_CENTER_TOKEN }} \ +# --quiet + + - name: Send Test APK + run: | + cd /opt/android/cake_wallet + var=$(curl --upload-file build/app/outputs/apk/release/app-release.apk https://transfer.sh/$GITHUB_HEAD_REF.apk -H "Max-Days: 10") + curl ${{ secrets.SLACK_WEB_HOOK }} -H "Content-Type: application/json" -d '{"apk_link": "'"$var"'","ticket": "'"$GITHUB_HEAD_REF"'"}' + + - name: Rename apk file + run: | + cd /opt/android/cake_wallet/build/app/outputs/apk/release + mkdir test-apk + mv app-release.apk test-apk/$GITHUB_HEAD_REF.apk + + - name: Upload Artifact + uses: kittaakos/upload-artifact-as-is@v0 + with: + path: /opt/android/cake_wallet/build/app/outputs/apk/release/test-apk/ diff --git a/android/app/build.gradle b/android/app/build.gradle index 74cd0f8f7..00cef6393 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -46,7 +46,7 @@ android { defaultConfig { applicationId appProperties['id'] minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index f43b0369b..22278d5f1 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -21,7 +21,8 @@ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" - android:screenOrientation="portrait"> + android:screenOrientation="portrait" + android:exported="true"> <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" diff --git a/assets/node_list.yml b/assets/node_list.yml index 09fb72383..fccc968cf 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -1,31 +1,24 @@ - - uri: xmr-node-uk.cakewallet.com:18081 + uri: xmr-node.cakewallet.com:18081 is_default: true - - uri: xmr-node-eu.cakewallet.com:18081 + uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081 is_default: false - - uri: xmr-node-usa-east.cakewallet.com:18081 + uri: node.sethforprivacy.com:18089 + is_default: false +- + uri: nodes.hashvault.pro:18081 + is_default: false +- + uri: node.c3pool.com:18081 + is_default: false +- + uri: node.supportxmr.com:18081 + is_default: false +- + uri: node.community.rino.io:18081 is_default: false - uri: node.moneroworld.com:18089 is_default: false -- - uri: node.xmr.pt:18081 - is_default: false -- - uri: node.monero.net:18081 - is_default: false -- - uri: opennode.xmr-tw.org:18089 - is_default: false -- - uri: node.imonero.org:18081 - is_default: false - - uri: node.c3pool.com:18081 - is_default: false - - uri: xmr.prprpr.icu:18081 - is_default: false - diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 7abed8c07..fad546d90 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -129,7 +129,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance, @override Future<void> startSync() async { try { - syncStatus = StartingSyncStatus(); + syncStatus = AttemptingSyncStatus(); await walletAddresses.discoverAddresses(); await updateTransactions(); _subscribeForUpdates(); diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 5fce69a8e..ff1d6b051 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -109,28 +109,28 @@ class Node extends HiveObject with Keyable { final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); final realm = 'monero-rpc'; final body = { - 'jsonrpc': '2.0', - 'id': '0', + 'jsonrpc': '2.0', + 'id': '0', 'method': 'get_info' }; try { final authenticatingClient = HttpClient(); - + authenticatingClient.addCredentials( rpcUri, - realm, + realm, HttpClientDigestCredentials(login ?? '', password ?? ''), ); - + final http.Client client = ioc.IOClient(authenticatingClient); - + final response = await client.post( rpcUri, headers: {'Content-Type': 'application/json'}, body: json.encode(body), ); - + client.close(); final resBody = json.decode(response.body) as Map<String, dynamic>; diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index fabec67c8..4983967d0 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -28,7 +28,7 @@ class NotConnectedSyncStatus extends SyncStatus { double progress() => 0.0; } -class StartingSyncStatus extends SyncStatus { +class AttemptingSyncStatus extends SyncStatus { @override double progress() => 0.0; } diff --git a/cw_haven/ios/Classes/haven_api.cpp b/cw_haven/ios/Classes/haven_api.cpp index c1013bf87..aecaf0016 100644 --- a/cw_haven/ios/Classes/haven_api.cpp +++ b/cw_haven/ios/Classes/haven_api.cpp @@ -927,6 +927,16 @@ extern "C" return static_cast<int32_t>(rates.size()); } + void set_trusted_daemon(bool arg) + { + m_wallet->setTrustedDaemon(arg); + } + + bool trusted_daemon() + { + return m_wallet->trustedDaemon(); + } + #ifdef __cplusplus } #endif diff --git a/cw_haven/lib/api/signatures.dart b/cw_haven/lib/api/signatures.dart index 774a303d7..31c911edc 100644 --- a/cw_haven/lib/api/signatures.dart +++ b/cw_haven/lib/api/signatures.dart @@ -137,4 +137,8 @@ typedef get_rate = Pointer<Int64> Function(); typedef size_of_rate = Int32 Function(); -typedef update_rate = Void Function(); \ No newline at end of file +typedef update_rate = Void Function(); + +typedef set_trusted_daemon = Void Function(Int8 trusted); + +typedef trusted_daemon = Int8 Function(); \ No newline at end of file diff --git a/cw_haven/lib/api/types.dart b/cw_haven/lib/api/types.dart index f1dc0e26b..de9ff74a0 100644 --- a/cw_haven/lib/api/types.dart +++ b/cw_haven/lib/api/types.dart @@ -135,4 +135,8 @@ typedef GetRate = Pointer<Int64> Function(); typedef SizeOfRate = int Function(); -typedef UpdateRate = void Function(); \ No newline at end of file +typedef UpdateRate = void Function(); + +typedef SetTrustedDaemon = void Function(int); + +typedef TrustedDaemon = int Function(); \ No newline at end of file diff --git a/cw_haven/lib/api/wallet.dart b/cw_haven/lib/api/wallet.dart index 2b85f60e2..bdf6a1af7 100644 --- a/cw_haven/lib/api/wallet.dart +++ b/cw_haven/lib/api/wallet.dart @@ -116,6 +116,14 @@ final rescanBlockchainAsyncNative = havenApi .lookup<NativeFunction<rescan_blockchain>>('rescan_blockchain') .asFunction<RescanBlockchainAsync>(); +final setTrustedDaemonNative = havenApi + .lookup<NativeFunction<set_trusted_daemon>>('set_trusted_daemon') + .asFunction<SetTrustedDaemon>(); + +final trustedDaemonNative = havenApi + .lookup<NativeFunction<trusted_daemon>>('trusted_daemon') + .asFunction<TrustedDaemon>(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -351,3 +359,7 @@ Future<bool> isConnected() => compute(_isConnected, 0); Future<int> getNodeHeight() => compute(_getNodeHeight, 0); void rescanBlockchainAsync() => rescanBlockchainAsyncNative(); + +Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); + +Future<bool> trustedDaemon() async => trustedDaemonNative() != 0; diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart index 4f360bdff..a4b949d8f 100644 --- a/cw_haven/lib/haven_wallet.dart +++ b/cw_haven/lib/haven_wallet.dart @@ -121,6 +121,8 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance, password: node.password, useSSL: node.useSSL ?? false, isLightWallet: false); // FIXME: hardcoded value + + haven_wallet.setTrustedDaemon(node.trusted); syncStatus = ConnectedSyncStatus(); } catch (e) { syncStatus = FailedSyncStatus(); @@ -135,7 +137,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance, } catch (_) {} try { - syncStatus = StartingSyncStatus(); + syncStatus = AttemptingSyncStatus(); haven_wallet.startRefresh(); _setListeners(); _listener?.start(); diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index 6bb251dcf..117214295 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -783,6 +783,16 @@ extern "C" return strdup(get_current_wallet()->getSubaddressLabel(accountIndex, addressIndex).c_str()); } + void set_trusted_daemon(bool arg) + { + m_wallet->setTrustedDaemon(arg); + } + + bool trusted_daemon() + { + return m_wallet->trustedDaemon(); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/ios/Classes/monero_api.h b/cw_monero/ios/Classes/monero_api.h index 3e2ebcff2..74258ba4c 100644 --- a/cw_monero/ios/Classes/monero_api.h +++ b/cw_monero/ios/Classes/monero_api.h @@ -30,6 +30,9 @@ void set_refresh_from_block_height(uint64_t height); void set_recovering_from_seed(bool is_recovery); void store(char *path); +void set_trusted_daemon(bool arg); +bool trusted_daemon(); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index f9e88153e..0b14fd557 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -125,4 +125,8 @@ typedef rescan_blockchain = Void Function(); typedef get_subaddress_label = Pointer<Utf8> Function( Int32 accountIndex, - Int32 addressIndex); \ No newline at end of file + Int32 addressIndex); + +typedef set_trusted_daemon = Void Function(Int8 trusted); + +typedef trusted_daemon = Int8 Function(); \ No newline at end of file diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 468ce93e2..c5918c12a 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -123,4 +123,8 @@ typedef RescanBlockchainAsync = void Function(); typedef GetSubaddressLabel = Pointer<Utf8> Function( int accountIndex, - int addressIndex); \ No newline at end of file + int addressIndex); + +typedef SetTrustedDaemon = void Function(int); + +typedef TrustedDaemon = int Function(); \ No newline at end of file diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 5c2f1bd27..7ddbf29dc 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -120,6 +120,14 @@ final getSubaddressLabelNative = moneroApi .lookup<NativeFunction<get_subaddress_label>>('get_subaddress_label') .asFunction<GetSubaddressLabel>(); +final setTrustedDaemonNative = moneroApi + .lookup<NativeFunction<set_trusted_daemon>>('set_trusted_daemon') + .asFunction<SetTrustedDaemon>(); + +final trustedDaemonNative = moneroApi + .lookup<NativeFunction<trusted_daemon>>('trusted_daemon') + .asFunction<TrustedDaemon>(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -359,4 +367,8 @@ void rescanBlockchainAsync() => rescanBlockchainAsyncNative(); String getSubaddressLabel(int accountIndex, int addressIndex) { return convertUTF8ToString(pointer: getSubaddressLabelNative(accountIndex, addressIndex)); -} \ No newline at end of file +} + +Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); + +Future<bool> trustedDaemon() async => trustedDaemonNative() != 0; \ No newline at end of file diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 404d8bad0..a9d708c05 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -136,6 +136,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance, password: node.password, useSSL: node.isSSL, isLightWallet: false); // FIXME: hardcoded value + + monero_wallet.setTrustedDaemon(node.trusted); syncStatus = ConnectedSyncStatus(); } catch (e) { syncStatus = FailedSyncStatus(); @@ -150,7 +152,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance, } catch (_) {} try { - syncStatus = StartingSyncStatus(); + syncStatus = AttemptingSyncStatus(); monero_wallet.startRefresh(); _setListeners(); _listener?.start(); diff --git a/howto-build-android.md b/howto-build-android.md index 28478cdd1..4ef385b9f 100644 --- a/howto-build-android.md +++ b/howto-build-android.md @@ -55,7 +55,7 @@ You may download and install the latest version of Android Studio [here](https:/ ### 3. Installing Flutter -Need to install flutter with version `2.0.4`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually). When flutter repository is downloaded please open it with `cd <flutter-path>` and checkout version 2.0.4 by `git checkout 2.0.4`. +Need to install flutter with version `3.x.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually). ### 4. Verify Installations @@ -66,7 +66,7 @@ Verify that the Android toolchain, Flutter, and Android Studio have been correct The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding. ``` Doctor summary (to see all details, run flutter doctor -v): -[✓] Flutter (Channel stable, 2.0.4, on Linux, locale en_US.UTF-8) +[✓] Flutter (Channel stable, 3.x.x, on Linux, locale en_US.UTF-8) [✓] Android toolchain - develop for Android devices (Android SDK version 28) [✓] Android Studio (version 4.0) ``` @@ -156,6 +156,10 @@ Generate mobx models for `cw_bitcoin`: `cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..` +Generate mobx models for `cw_haven`: + +`cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..` + Finally build mobx models for the app: `$ flutter packages pub run build_runner build --delete-conflicting-outputs` diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index e46ec2490..66094de2b 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -14,8 +14,8 @@ String syncStatusTitle(SyncStatus syncStatus) { return S.current.sync_status_not_connected; } - if (syncStatus is StartingSyncStatus) { - return S.current.sync_status_starting_sync; + if (syncStatus is AttemptingSyncStatus) { + return S.current.sync_status_attempting_sync; } if (syncStatus is FailedSyncStatus) { diff --git a/lib/di.dart b/lib/di.dart index 7a3a8631c..a896bf1e4 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -5,9 +5,14 @@ import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; +import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; +import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; +import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; +import 'package:cake_wallet/src/screens/settings/security_backup_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart'; +import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart'; @@ -26,7 +31,11 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; -import 'package:cake_wallet/view_model/privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart'; +import 'package:cake_wallet/view_model/privacy_settings_view_model.dart' import 'package:cw_core/unspent_coins_info.dart'; import 'package:cake_wallet/core/backup_service.dart'; import 'package:cw_core/wallet_service.dart'; @@ -50,7 +59,6 @@ import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart' import 'package:cake_wallet/src/screens/faq/faq_page.dart'; import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart'; import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart'; -import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart'; import 'package:cake_wallet/src/screens/order_details/order_details_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/rescan/rescan_page.dart'; @@ -60,7 +68,6 @@ import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; import 'package:cake_wallet/src/screens/send/send_template_page.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/support/support_page.dart'; import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; @@ -116,7 +123,6 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_v import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart'; -import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_keys_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; @@ -378,8 +384,7 @@ Future setup( _transactionDescriptionBox)); getIt.registerFactory( - () => SendPage(sendViewModel: getIt.get<SendViewModel>(), - settingsViewModel: getIt.get<SettingsViewModel>())); + () => SendPage(sendViewModel: getIt.get<SendViewModel>())); getIt.registerFactory(() => SendTemplatePage( sendTemplateViewModel: getIt.get<SendTemplateViewModel>())); @@ -435,12 +440,20 @@ Future setup( getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account))); getIt.registerFactory(() { - final appStore = getIt.get<AppStore>(); - final yatStore = getIt.get<YatStore>(); - return SettingsViewModel(appStore.settingsStore, yatStore, appStore.wallet!); + return DisplaySettingsViewModel(getIt.get<SettingsStore>()); }); - getIt.registerFactory(() => SettingsPage(getIt.get<SettingsViewModel>())); + getIt.registerFactory(() { + return PrivacySettingsViewModel(getIt.get<SettingsStore>()); + }); + + getIt.registerFactory(() { + return OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!); + }); + + getIt.registerFactory(() { + return SecuritySettingsViewModel(getIt.get<SettingsStore>()); + }); getIt .registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!)); @@ -476,7 +489,15 @@ Future setup( _nodeSource, appStore.wallet!, appStore.settingsStore); }); - getIt.registerFactory(() => NodeListPage(getIt.get<NodeListViewModel>())); + getIt.registerFactory(() => ConnectionSyncPage(getIt.get<NodeListViewModel>(), getIt.get<DashboardViewModel>())); + + getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>())); + + getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>())); + + getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>())); + + getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>())); getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>( (WalletType? type, _) => @@ -496,7 +517,6 @@ Future setup( getIt.get<TradesStore>(), getIt.get<AppStore>().settingsStore, getIt.get<SharedPreferences>(), - getIt.get<SettingsViewModel>(), )); getIt.registerFactory(() => ExchangeTradeViewModel( @@ -703,6 +723,7 @@ Future setup( ioniaAnyPayService: getIt.get<IoniaAnyPay>(), amount: amount, ioniaMerchant: merchant, + sendViewModel: getIt.get<SendViewModel>() ); }); @@ -796,7 +817,7 @@ Future setup( => IoniaPaymentStatusPage(getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo))); getIt.registerFactoryParam<PrivacySettingsViewModel, WalletType, void>((type, _) => - PrivacySettingsViewModel(type)); + PrivacySettingsViewModel(type)) _isSetupFinished = true; } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index b2036c3f6..9aff05500 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -69,6 +69,8 @@ Future defaultSettingsMigration( sharedPreferences: sharedPreferences, nodes: nodes); await changeLitecoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); + await changeHavenCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); break; case 2: @@ -133,19 +135,32 @@ Future defaultSettingsMigration( await changeDefaultHavenNode(nodes); break; + case 18: + await addOnionNode(nodes); + break; + default: break; } await sharedPreferences.setInt( - 'current_default_settings_migration_version', version); + PreferencesKey.currentDefaultSettingsMigrationVersion, version); } catch (e) { print('Migration error: ${e.toString()}'); } }); await sharedPreferences.setInt( - 'current_default_settings_migration_version', version); + PreferencesKey.currentDefaultSettingsMigrationVersion, version); +} + +Future<void> addOnionNode(Box<Node> nodes) async { + final onionNodeUri = "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081"; + + // check if the user has this node before (added it manually) + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == onionNodeUri) == null) { + await nodes.add(Node(uri: onionNodeUri, type: WalletType.monero)); + } } Future<void> replaceNodesMigration({required Box<Node> nodes}) async { @@ -176,7 +191,7 @@ Future<void> changeMoneroCurrentNodeToDefault( final node = getMoneroDefaultNode(nodes: nodes); final nodeId = node?.key as int ?? 0; // 0 - England - await sharedPreferences.setInt('current_node_id', nodeId); + await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, nodeId); } Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) { @@ -223,7 +238,7 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault( final server = getBitcoinDefaultElectrumServer(nodes: nodes); final serverId = server?.key as int ?? 0; - await sharedPreferences.setInt('current_node_id_btc', serverId); + await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId); } Future<void> changeLitecoinCurrentElectrumServerToDefault( @@ -232,7 +247,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault( final server = getLitecoinDefaultElectrumServer(nodes: nodes); final serverId = server?.key as int ?? 0; - await sharedPreferences.setInt('current_node_id_ltc', serverId); + await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId); } Future<void> changeHavenCurrentNodeToDefault( @@ -252,7 +267,7 @@ Future<void> replaceDefaultNode( 'eu-node.cakewallet.io:18081', 'node.cakewallet.io:18081' ]; - final currentNodeId = sharedPreferences.getInt('current_node_id'); + final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentNode = nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId); final needToReplace = @@ -277,17 +292,29 @@ Future<void> updateNodeTypes({required Box<Node> nodes}) async { Future<void> addBitcoinElectrumServerList({required Box<Node> nodes}) async { final serverList = await loadBitcoinElectrumServerList(); - await nodes.addAll(serverList); + for (var node in serverList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } } Future<void> addLitecoinElectrumServerList({required Box<Node> nodes}) async { final serverList = await loadLitecoinElectrumServerList(); - await nodes.addAll(serverList); + for (var node in serverList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } } Future<void> addHavenNodeList({required Box<Node> nodes}) async { final nodeList = await loadDefaultHavenNodes(); - await nodes.addAll(nodeList); + for (var node in nodeList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } } Future<void> addAddressesForMoneroWallets( @@ -431,7 +458,7 @@ Future<void> resetBitcoinElectrumServer( final oldElectrumServer = nodeSource.values.firstWhereOrNull( (node) => node.uri.toString().contains('electrumx.cakewallet.com')); var cakeWalletNode = nodeSource.values.firstWhereOrNull( - (node) => node.uri.toString() == cakeWalletBitcoinElectrumUri); + (node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri); if (cakeWalletNode == null) { cakeWalletNode = diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 6cf7e5608..a28410192 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -11,6 +11,7 @@ class PreferencesKey { static const shouldSaveRecipientAddressKey = 'save_recipient_address'; static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; + static const disableExchangeKey = 'disable_exchange'; static const currentTheme = 'current_theme'; static const isDarkThemeLegacy = 'dark_theme'; static const displayActionListModeKey = 'display_list_mode'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart new file mode 100644 index 000000000..927ab8803 --- /dev/null +++ b/lib/entities/priority_for_wallet_type.dart @@ -0,0 +1,21 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/monero/monero.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_type.dart'; + +List<TransactionPriority> priorityForWalletType(WalletType type) { + switch (type) { + case WalletType.monero: + return monero!.getTransactionPriorities(); + case WalletType.bitcoin: + return bitcoin!.getTransactionPriorities(); + case WalletType.litecoin: + return bitcoin!.getLitecoinTransactionPriorities(); + case WalletType.haven: + return haven!.getTransactionPriorities(); + default: + return []; + } +} + diff --git a/lib/exchange/changenow/changenow_exchange_provider.dart b/lib/exchange/changenow/changenow_exchange_provider.dart index 20f529733..96b6142e6 100644 --- a/lib/exchange/changenow/changenow_exchange_provider.dart +++ b/lib/exchange/changenow/changenow_exchange_provider.dart @@ -114,7 +114,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final uri = Uri.https(apiAuthority, createTradePath); final response = await post(uri, headers: headers, body: json.encode(body)); - + if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map<String, dynamic>; final error = responseJSON['error'] as String; @@ -130,7 +130,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final id = responseJSON['id'] as String; final inputAddress = responseJSON['payinAddress'] as String; final refundAddress = responseJSON['refundAddress'] as String; - final extraId = responseJSON['payinExtraId'] as String; + final extraId = responseJSON['payinExtraId'] as String?; return Trade( id: id, diff --git a/lib/main.dart b/lib/main.dart index 3cd5679b3..e1fade7b4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -128,7 +128,7 @@ Future<void> main() async { exchangeTemplates: exchangeTemplates, transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, - initialMigrationVersion: 17); + initialMigrationVersion: 18); runApp(App()); } catch (e, stacktrace) { runApp(MaterialApp( diff --git a/lib/router.dart b/lib/router.dart index 214e732ff..f22fd3e18 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -5,6 +5,10 @@ import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; import 'package:cake_wallet/src/screens/buy/pre_order_page.dart'; +import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; +import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; +import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; +import 'package:cake_wallet/src/screens/settings/security_backup_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart'; @@ -17,6 +21,7 @@ import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; +import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; @@ -39,7 +44,6 @@ import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart'; import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart'; -import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart'; import 'package:cake_wallet/src/screens/receive/receive_page.dart'; import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart'; import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart'; @@ -59,7 +63,6 @@ import 'package:cake_wallet/src/screens/contact/contact_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/restore/restore_wallet_from_seed_details.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; -import 'package:cake_wallet/src/screens/settings/settings.dart'; import 'package:cake_wallet/src/screens/rescan/rescan_page.dart'; import 'package:cake_wallet/src/screens/faq/faq_page.dart'; import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; @@ -277,10 +280,26 @@ Route<dynamic> createRoute(RouteSettings settings) { param2: false), onWillPop: () async => false)); - case Routes.nodeList: + case Routes.connectionSync: return CupertinoPageRoute<void>( - builder: (_) => getIt.get<NodeListPage>()); + builder: (_) => getIt.get<ConnectionSyncPage>()); + case Routes.securityBackupPage: + return CupertinoPageRoute<void>( + builder: (_) => getIt.get<SecurityBackupPage>()); + + case Routes.privacyPage: + return CupertinoPageRoute<void>( + builder: (_) => getIt.get<PrivacyPage>()); + + case Routes.displaySettingsPage: + return CupertinoPageRoute<void>( + builder: (_) => getIt.get<DisplaySettingsPage>()); + + case Routes.otherSettingsPage: + return CupertinoPageRoute<void>( + builder: (_) => getIt.get<OtherSettingsPage>()); + case Routes.newNode: return CupertinoPageRoute<void>( builder: (_) => getIt.get<NodeCreateOrEditPage>()); @@ -363,9 +382,6 @@ Route<dynamic> createRoute(RouteSettings settings) { return CupertinoPageRoute<void>( builder: (_) => getIt.get<ExchangeTemplatePage>()); - case Routes.settings: - return MaterialPageRoute<void>(builder: (_) => getIt.get<SettingsPage>()); - case Routes.rescan: return MaterialPageRoute<void>(builder: (_) => getIt.get<RescanPage>()); diff --git a/lib/routes.dart b/lib/routes.dart index 1a5a5832f..5ea250310 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -21,7 +21,6 @@ class Routes { static const seedLanguage = '/seed_language'; static const walletList = '/view_model.wallet_list'; static const auth = '/auth'; - static const nodeList = '/node_list'; static const newNode = '/new_node_list'; static const login = '/login'; static const splash = '/splash'; @@ -77,5 +76,10 @@ class Routes { static const ioniaMoreOptionsPage = '/ionia_more_options_page'; static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page'; static const onramperPage = '/onramper'; - static const privacySettings = '/privacy_settings'; + static const connectionSync = '/connection_sync_page'; + static const securityBackupPage = '/security_and_backup_page'; + static const privacyPage = '/privacy_page'; + static const displaySettingsPage = '/display_settings_page'; + static const otherSettingsPage = '/other_settings_page'; + static const advancedPrivacySettings = '/advanced_privacy_settings'; } diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index f3d5a21fb..c21365aa4 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -60,7 +60,7 @@ class DashboardPage extends BasePage { Widget middle(BuildContext context) { return SyncIndicator(dashboardViewModel: walletViewModel, onTap: () => Navigator.of(context, rootNavigator: true) - .pushNamed(Routes.nodeList)); + .pushNamed(Routes.connectionSync)); } @override @@ -315,6 +315,8 @@ class DashboardPage extends BasePage { } Future<void> _onClickExchangeButton(BuildContext context) async { - await Navigator.of(context).pushNamed(Routes.exchange); + if (walletViewModel.isEnabledExchangeAction) { + await Navigator.of(context).pushNamed(Routes.exchange); + } } } diff --git a/lib/src/screens/dashboard/wallet_menu.dart b/lib/src/screens/dashboard/wallet_menu.dart index 68e7cc76d..bc1f20a5d 100644 --- a/lib/src/screens/dashboard/wallet_menu.dart +++ b/lib/src/screens/dashboard/wallet_menu.dart @@ -1,81 +1,64 @@ import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/screens/dashboard/wallet_menu_item.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/auth/auth_page.dart'; -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; // FIXME: terrible design class WalletMenu { WalletMenu(this.context, this.reconnect, this.hasRescan) : items = [] { items.addAll([ - WalletMenuItem( - title: S.current.reconnect, - image: Image.asset('assets/images/reconnect_menu.png', - height: 16, width: 16), - handler: () => _presentReconnectAlert(context)), - if (hasRescan) - WalletMenuItem( - title: S.current.rescan, - image: Image.asset('assets/images/filter_icon.png', - height: 16, width: 16, color: Palette.darkBlue), - handler: () => Navigator.of(context).pushNamed(Routes.rescan)), - WalletMenuItem( - title: S.current.wallets, - image: Image.asset('assets/images/wallet_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.walletList)), - WalletMenuItem( - title: S.current.nodes, - image: Image.asset('assets/images/nodes_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.nodeList)), - WalletMenuItem( - title: S.current.show_keys, - image: - Image.asset('assets/images/key_menu.png', height: 16, width: 16), - handler: () { - Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - if (isAuthenticatedSuccessfully) { - auth.close(route: Routes.showKeys); - } - }); - }), - WalletMenuItem( - title: S.current.address_book_menu, - image: Image.asset('assets/images/open_book_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.addressBook)), - WalletMenuItem( - title: S.current.backup, - image: Image.asset('assets/images/restore_wallet.png', - height: 16, - width: 16, - color: Palette.darkBlue), - handler: () { - Navigator.of(context).pushNamed( - Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - if (isAuthenticatedSuccessfully) { - auth.close(route:Routes.backup); - } - }); - }), - WalletMenuItem( - title: S.current.settings_title, - image: Image.asset('assets/images/settings_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.settings)), - WalletMenuItem( - title: S.current.settings_support, - image: Image.asset('assets/images/question_mark.png', - height: 16, width: 16, color: Palette.darkBlue), - handler: () => Navigator.of(context).pushNamed(Routes.support)), + WalletMenuItem( + title: S.current.connection_sync, + image: Image.asset('assets/images/nodes_menu.png', + height: 16, width: 16), + handler: () => Navigator.of(context).pushNamed(Routes.connectionSync), + ), + WalletMenuItem( + title: S.current.wallets, + image: Image.asset('assets/images/wallet_menu.png', + height: 16, width: 16), + handler: () => Navigator.of(context).pushNamed(Routes.walletList), + ), + WalletMenuItem( + title: S.current.security_and_backup, + image: + Image.asset('assets/images/key_menu.png', height: 16, width: 16), + handler: () { + Navigator.of(context).pushNamed(Routes.securityBackupPage); + }), + WalletMenuItem( + title: S.current.privacy, + image: + Image.asset('assets/images/eye_menu.png', height: 16, width: 16), + handler: () { + Navigator.of(context).pushNamed(Routes.privacyPage); + }), + WalletMenuItem( + title: S.current.address_book_menu, + image: Image.asset('assets/images/open_book_menu.png', + height: 16, width: 16), + handler: () => Navigator.of(context).pushNamed(Routes.addressBook), + ), + WalletMenuItem( + title: S.current.display_settings, + image: Image.asset('assets/images/eye_menu.png', + height: 16, width: 16), + handler: () => Navigator.of(context).pushNamed(Routes.displaySettingsPage), + ), + WalletMenuItem( + title: S.current.other_settings, + image: Image.asset('assets/images/settings_menu.png', + height: 16, width: 16), + handler: () => Navigator.of(context).pushNamed(Routes.otherSettingsPage), + ), + WalletMenuItem( + title: S.current.settings_support, + image: Image.asset('assets/images/question_mark.png', + height: 16, width: 16, color: Palette.darkBlue), + handler: () => Navigator.of(context).pushNamed(Routes.support), + ), ]); } @@ -86,23 +69,6 @@ class WalletMenu { void action(int index) { final item = items[index]; - item?.handler(); - } - - Future<void> _presentReconnectAlert(BuildContext context) async { - await showPopUp<void>( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).reconnection, - alertContent: S.of(context).reconnect_alert_text, - rightButtonText: S.of(context).ok, - leftButtonText: S.of(context).cancel, - actionRightButton: () async { - Navigator.of(context).pop(); - await reconnect?.call(); - }, - actionLeftButton: () => Navigator.of(context).pop()); - }); + item.handler(); } } diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index f3b12e65b..4b9f0a220 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -3,7 +3,6 @@ import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; @@ -18,6 +17,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart'; class IoniaBuyGiftCardDetailPage extends BasePage { IoniaBuyGiftCardDetailPage(this.ioniaPurchaseViewModel); @@ -295,73 +295,35 @@ class IoniaBuyGiftCardDetailPage extends BasePage { final amount = ioniaPurchaseViewModel.invoice!.totalAmount; final addresses = ioniaPurchaseViewModel.invoice!.outAddresses; + ioniaPurchaseViewModel.sendViewModel.outputs.first.setCryptoAmount(amount); + ioniaPurchaseViewModel.sendViewModel.outputs.first.address = addresses.first; await showPopUp<void>( context: context, builder: (_) { - return IoniaConfirmModal( + return ConfirmSendingAlert( alertTitle: S.of(context).confirm_sending, - alertContent: Container( - height: 200, - padding: EdgeInsets.all(15), - child: Column(children: [ - Row(children: [ - Text(S.of(context).payment_id, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: PaletteDark.pigeonBlue, - decoration: TextDecoration.none)), - Text(ioniaPurchaseViewModel.invoice!.paymentId, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: PaletteDark.pigeonBlue, - decoration: TextDecoration.none)) - ], mainAxisAlignment: MainAxisAlignment.spaceBetween), - SizedBox(height: 10), - Row(children: [ - Text(S.of(context).amount, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: PaletteDark.pigeonBlue, - decoration: TextDecoration.none)), - Text('$amount ${ioniaPurchaseViewModel.invoice!.chain}', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: PaletteDark.pigeonBlue, - decoration: TextDecoration.none)) - ], mainAxisAlignment: MainAxisAlignment.spaceBetween), - SizedBox(height: 25), - Row(children: [ - Text(S.of(context).recipient_address, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: PaletteDark.pigeonBlue, - decoration: TextDecoration.none)) - ], mainAxisAlignment: MainAxisAlignment.center), - Expanded( - child: ListView.builder( - itemBuilder: (_, int index) { - return Text(addresses[index], - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: PaletteDark.pigeonBlue, - decoration: TextDecoration.none)); - }, - itemCount: addresses.length, - physics: NeverScrollableScrollPhysics())) - ])), + paymentId: S.of(context).payment_id, + paymentIdValue: ioniaPurchaseViewModel.invoice!.paymentId, + amount: S.of(context).send_amount, + amountValue: '$amount ${ioniaPurchaseViewModel.invoice!.chain}', + fiatAmountValue: + '~ ${ioniaPurchaseViewModel.sendViewModel.outputs.first.fiatAmount} ' + '${ioniaPurchaseViewModel.sendViewModel.fiat.title}', + fee: S.of(context).send_fee, + feeValue: + '${ioniaPurchaseViewModel.sendViewModel.outputs.first.estimatedFee} ' + '${ioniaPurchaseViewModel.invoice!.chain}', + feeFiatAmount: + '${ioniaPurchaseViewModel.sendViewModel.outputs.first.estimatedFeeFiatAmount} ' + '${ioniaPurchaseViewModel.sendViewModel.fiat.title}', + outputs: ioniaPurchaseViewModel.sendViewModel.outputs, rightButtonText: S.of(context).ok, leftButtonText: S.of(context).cancel, - leftActionColor: Color(0xffFF6600), - rightActionColor: Theme.of(context).accentTextTheme!.bodyText1!.color!, + alertLeftActionButtonTextColor: Colors.white, + alertRightActionButtonTextColor: Colors.white, + alertLeftActionButtonColor: Palette.brightOrange, + alertRightActionButtonColor: Theme.of(context).textTheme!.subtitle2!.color, actionRightButton: () async { Navigator.of(context).pop(); await ioniaPurchaseViewModel.commitPaymentInvoice(); diff --git a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart index 2fbb1d57d..1c13f7a88 100644 --- a/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_manage_cards_page.dart @@ -118,7 +118,7 @@ class IoniaManageCardsPage extends BasePage { width: 32, padding: EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), + color: Theme.of(context).textTheme!.headline6!.backgroundColor!, border: Border.all( color: Colors.white.withOpacity(0.2), ), diff --git a/lib/src/screens/ionia/widgets/confirm_modal.dart b/lib/src/screens/ionia/widgets/confirm_modal.dart deleted file mode 100644 index cfc9a1cf1..000000000 --- a/lib/src/screens/ionia/widgets/confirm_modal.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'dart:ui'; - -import 'package:cake_wallet/palette.dart'; -import 'package:flutter/material.dart'; - -class IoniaConfirmModal extends StatelessWidget { - IoniaConfirmModal({ - required this.alertTitle, - required this.alertContent, - required this.leftButtonText, - required this.rightButtonText, - required this.actionLeftButton, - required this.actionRightButton, - required this.leftActionColor, - required this.rightActionColor, - this.hideActions = false, - }); - - final String alertTitle; - final Widget alertContent; - final String leftButtonText; - final String rightButtonText; - final VoidCallback actionLeftButton; - final VoidCallback actionRightButton; - final Color leftActionColor; - final Color rightActionColor; - final bool hideActions; - - Widget actionButtons(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - IoniaActionButton( - buttonText: leftButtonText, - action: actionLeftButton, - backgoundColor: leftActionColor, - ), - Container( - width: 1, - height: 52, - color: Theme.of(context).dividerColor, - ), - IoniaActionButton( - buttonText: rightButtonText, - action: actionRightButton, - backgoundColor: rightActionColor, - ), - ], - ); - } - - Widget title(BuildContext context) { - return Text( - alertTitle, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme!.headline6!.color!, - decoration: TextDecoration.none, - ), - ); - } - - @override - Widget build(BuildContext context) { - return Container( - color: Colors.transparent, - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), - child: Container( - decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)), - child: Center( - child: GestureDetector( - onTap: () => null, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - width: 327, - color: Theme.of(context).accentTextTheme!.headline6!.decorationColor!, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(24, 20, 24, 0), - child: title(context), - ), - Padding( - padding: EdgeInsets.only(top: 16, bottom: 8), - child: Container( - height: 1, - color: Theme.of(context).dividerColor, - ), - ), - alertContent, - actionButtons(context), - ], - ), - ), - ), - ), - ), - ), - ), - ); - } -} - -class IoniaActionButton extends StatelessWidget { - const IoniaActionButton({ - required this.buttonText, - required this.action, - required this.backgoundColor, - }); - - final String buttonText; - final VoidCallback action; - final Color backgoundColor; - - @override - Widget build(BuildContext context) { - return Flexible( - child: Container( - height: 52, - padding: EdgeInsets.only(left: 6, right: 6), - color: backgoundColor, - child: ButtonTheme( - minWidth: double.infinity, - child: TextButton( - onPressed: action, - // FIX-ME: ignored highlightColor and splashColor - //highlightColor: Colors.transparent, - //splashColor: Colors.transparent, - child: Text( - buttonText, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: backgoundColor != null ? Colors.white : Theme.of(context).primaryTextTheme!.bodyText2!.backgroundColor!, - decoration: TextDecoration.none, - ), - )), - ), - )); - } -} diff --git a/lib/src/screens/ionia/widgets/ionia_filter_modal.dart b/lib/src/screens/ionia/widgets/ionia_filter_modal.dart index ec6e84cc0..2b885f736 100644 --- a/lib/src/screens/ionia/widgets/ionia_filter_modal.dart +++ b/lib/src/screens/ionia/widgets/ionia_filter_modal.dart @@ -20,7 +20,7 @@ class IoniaFilterModal extends StatelessWidget { padding: EdgeInsets.all(10), child: Image.asset( 'assets/images/mini_search_icon.png', - color: Theme.of(context).accentColor, + color: Theme.of(context).textTheme.subtitle2!.color!, ), ); return Scaffold( @@ -53,7 +53,7 @@ class IoniaFilterModal extends StatelessWidget { prefixIcon: searchIcon, hintText: S.of(context).search_category, contentPadding: EdgeInsets.only(bottom: 5), - fillColor: Theme.of(context).textTheme!.subtitle1!.backgroundColor!, + fillColor: Theme.of(context).primaryTextTheme!.caption!.decorationColor!.withOpacity(0.5), border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.circular(8), diff --git a/lib/src/screens/nodes/nodes_list_page.dart b/lib/src/screens/nodes/nodes_list_page.dart deleted file mode 100644 index 556a0fd06..000000000 --- a/lib/src/screens/nodes/nodes_list_page.dart +++ /dev/null @@ -1,166 +0,0 @@ -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cw_core/node.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/palette.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; -import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; - -class NodeListPage extends BasePage { - NodeListPage(this.nodeListViewModel); - - @override - String get title => S.current.nodes; - - final NodeListViewModel nodeListViewModel; - - @override - Widget trailing(context) { - return Container( - height: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(16)), - color: Theme.of(context).accentTextTheme.caption!.color!), - child: ButtonTheme( - minWidth: double.minPositive, - child: TextButton( - onPressed: () async { - await showPopUp<void>( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).node_reset_settings_title, - alertContent: - S.of(context).nodes_list_reset_to_default_message, - rightButtonText: S.of(context).reset, - leftButtonText: S.of(context).cancel, - actionRightButton: () async { - Navigator.of(context).pop(); - await nodeListViewModel.reset(); - }, - actionLeftButton: () => Navigator.of(context).pop()); - }); - }, - child: Text( - S.of(context).reset, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.w600, - color: Palette.blueCraiola), - )), - ), - ); - } - - @override - Widget body(BuildContext context) { - return Container( - padding: EdgeInsets.only(top: 10), - child: Observer( - builder: (BuildContext context) { - return SectionStandardList( - sectionCount: 2, - context: context, - itemCounter: (int sectionIndex) { - if (sectionIndex == 0) { - return 1; - } - - return nodeListViewModel.nodes.length; - }, - itemBuilder: (_, sectionIndex, index) { - if (sectionIndex == 0) { - return NodeHeaderListRow( - title: S.of(context).add_new_node, - onTap: (_) async => await Navigator.of(context) - .pushNamed(Routes.newNode)); - } - - final node = nodeListViewModel.nodes[index]; - final isSelected = - node.keyIndex == nodeListViewModel.currentNode.keyIndex; - final nodeListRow = NodeListRow( - title: node.uriRaw, - isSelected: isSelected, - isAlive: node.requestNode(), - onTap: (_) async { - if (isSelected) { - return; - } - - await showPopUp<void>( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: - S.of(context).change_current_node_title, - alertContent: S - .of(context) - .change_current_node(node.uriRaw), - leftButtonText: S.of(context).cancel, - rightButtonText: S.of(context).change, - actionLeftButton: () => - Navigator.of(context).pop(), - actionRightButton: () async { - await nodeListViewModel.setAsCurrent(node); - Navigator.of(context).pop(); - }); - }); - }); - - final dismissibleRow = Slidable( - key: Key('${node.keyIndex}'), - startActionPane: _actionPane(context, node), - endActionPane: _actionPane(context, node), - child: nodeListRow, - ); - - return isSelected ? nodeListRow : dismissibleRow; - }); - }, - ), - ); - } - - ActionPane _actionPane(BuildContext context, Node node) => ActionPane( - motion: const ScrollMotion(), - extentRatio: 0.3, - children: [ - SlidableAction( - onPressed: (context) async { - final confirmed = await showPopUp<bool>( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).remove_node, - alertContent: - S.of(context).remove_node_message, - rightButtonText: S.of(context).remove, - leftButtonText: S.of(context).cancel, - actionRightButton: () => - Navigator.pop(context, true), - actionLeftButton: () => - Navigator.pop(context, false)); - }) ?? - false; - - if (confirmed) { - await nodeListViewModel.delete(node); - } - }, - backgroundColor: Colors.red, - foregroundColor: Colors.white, - icon: CupertinoIcons.delete, - label: S.of(context).delete, - ), - ], - ); -} diff --git a/lib/src/screens/nodes/widgets/node_list_row.dart b/lib/src/screens/nodes/widgets/node_list_row.dart index 3008d450c..580aba170 100644 --- a/lib/src/screens/nodes/widgets/node_list_row.dart +++ b/lib/src/screens/nodes/widgets/node_list_row.dart @@ -37,7 +37,10 @@ class NodeHeaderListRow extends StandardListRow { @override Widget buildTrailing(BuildContext context) { - return Icon(Icons.add, - color: Theme.of(context).accentTextTheme!.subtitle1!.color!, size: 24.0); + return SizedBox( + width: 20, + child: Icon(Icons.add, + color: Theme.of(context).accentTextTheme!.subtitle1!.color!, size: 24.0), + ); } } diff --git a/lib/src/screens/receive/widgets/qr_painter.dart b/lib/src/screens/receive/widgets/qr_painter.dart index 90dba73ac..e4af59f1a 100644 --- a/lib/src/screens/receive/widgets/qr_painter.dart +++ b/lib/src/screens/receive/widgets/qr_painter.dart @@ -9,7 +9,6 @@ class QrPainter extends CustomPainter { this.errorCorrectionLevel, ) : this._qr = QrCode(version, errorCorrectionLevel)..addData(data) { _p.color = this.color; - _qr.addData(data); _qrImage = QrImage(_qr); } diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index e0cfff517..b051a3afb 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -1,4 +1,3 @@ -import 'dart:ui'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; import 'package:cake_wallet/src/screens/send/widgets/send_card.dart'; @@ -6,8 +5,6 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/template_tile.dart'; import 'package:cake_wallet/view_model/send/output.dart'; -import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; @@ -28,13 +25,11 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:cw_core/crypto_currency.dart'; class SendPage extends BasePage { - SendPage({required this.sendViewModel,required this.settingsViewModel }) : _formKey = GlobalKey<FormState>(),fiatFromSettings = settingsViewModel.fiatCurrency; + SendPage({required this.sendViewModel}) : _formKey = GlobalKey<FormState>(); final SendViewModel sendViewModel; - final SettingsViewModel settingsViewModel; final GlobalKey<FormState> _formKey; final controller = PageController(initialPage: 0); - final FiatCurrency fiatFromSettings ; bool _effectsInstalled = false; @@ -55,7 +50,7 @@ class SendPage extends BasePage { @override void onClose(BuildContext context) { - settingsViewModel.setFiatCurrency(fiatFromSettings); + sendViewModel.onClose(); Navigator.of(context).pop(); } @@ -236,7 +231,7 @@ class SendPage extends BasePage { if(template.isCurrencySelected){ output.setCryptoAmount(template.amount); }else{ - settingsViewModel.setFiatCurrency(fiatFromTemplate); + sendViewModel.setFiatCurrency(fiatFromTemplate); output.setFiatAmount(template.amountFiat); } output.resetParsedAddress(); diff --git a/lib/src/screens/send/widgets/confirm_sending_alert.dart b/lib/src/screens/send/widgets/confirm_sending_alert.dart index 317303442..a034d801e 100644 --- a/lib/src/screens/send/widgets/confirm_sending_alert.dart +++ b/lib/src/screens/send/widgets/confirm_sending_alert.dart @@ -4,10 +4,13 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/base_alert_dialog.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/cake_scrollbar.dart'; +import 'package:flutter/scheduler.dart'; class ConfirmSendingAlert extends BaseAlertDialog { ConfirmSendingAlert({ required this.alertTitle, + this.paymentId, + this.paymentIdValue, required this.amount, required this.amountValue, required this.fiatAmountValue, @@ -19,9 +22,15 @@ class ConfirmSendingAlert extends BaseAlertDialog { required this.rightButtonText, required this.actionLeftButton, required this.actionRightButton, - this.alertBarrierDismissible = true}); + this.alertBarrierDismissible = true, + this.alertLeftActionButtonTextColor, + this.alertRightActionButtonTextColor, + this.alertLeftActionButtonColor, + this.alertRightActionButtonColor}); final String alertTitle; + final String? paymentId; + final String? paymentIdValue; final String amount; final String amountValue; final String fiatAmountValue; @@ -34,6 +43,10 @@ class ConfirmSendingAlert extends BaseAlertDialog { final VoidCallback actionLeftButton; final VoidCallback actionRightButton; final bool alertBarrierDismissible; + final Color? alertLeftActionButtonTextColor; + final Color? alertRightActionButtonTextColor; + final Color? alertLeftActionButtonColor; + final Color? alertRightActionButtonColor; @override String get titleText => alertTitle; @@ -56,8 +69,22 @@ class ConfirmSendingAlert extends BaseAlertDialog { @override bool get barrierDismissible => alertBarrierDismissible; + @override + Color? get leftActionButtonTextColor => alertLeftActionButtonTextColor; + + @override + Color? get rightActionButtonTextColor => alertRightActionButtonTextColor; + + @override + Color? get leftActionButtonColor => alertLeftActionButtonColor; + + @override + Color? get rightActionButtonColor => alertRightActionButtonColor; + @override Widget content(BuildContext context) => ConfirmSendingAlertContent( + paymentId: paymentId, + paymentIdValue: paymentIdValue, amount: amount, amountValue: amountValue, fiatAmountValue: fiatAmountValue, @@ -70,6 +97,8 @@ class ConfirmSendingAlert extends BaseAlertDialog { class ConfirmSendingAlertContent extends StatefulWidget { ConfirmSendingAlertContent({ + this.paymentId, + this.paymentIdValue, required this.amount, required this.amountValue, required this.fiatAmountValue, @@ -78,6 +107,8 @@ class ConfirmSendingAlertContent extends StatefulWidget { required this.feeFiatAmount, required this.outputs}); + final String? paymentId; + final String? paymentIdValue; final String amount; final String amountValue; final String fiatAmountValue; @@ -88,6 +119,8 @@ class ConfirmSendingAlertContent extends StatefulWidget { @override ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState( + paymentId: paymentId, + paymentIdValue: paymentIdValue, amount: amount, amountValue: amountValue, fiatAmountValue: fiatAmountValue, @@ -100,6 +133,8 @@ class ConfirmSendingAlertContent extends StatefulWidget { class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> { ConfirmSendingAlertContentState({ + this.paymentId, + this.paymentIdValue, required this.amount, required this.amountValue, required this.fiatAmountValue, @@ -115,6 +150,8 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> : S.current.recipient_address; } + final String? paymentId; + final String? paymentIdValue; final String amount; final String amountValue; final String fiatAmountValue; @@ -129,6 +166,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> double fromTop = 0; String recipientTitle; int itemCount; + bool showScrollbar = false; @override Widget build(BuildContext context) { @@ -140,6 +178,12 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> setState(() {}); }); + SchedulerBinding.instance.addPostFrameCallback((_) { + setState(() { + showScrollbar = controller.position.maxScrollExtent > 0; + }); + }); + return Stack( alignment: Alignment.center, clipBehavior: Clip.none, @@ -150,6 +194,44 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> controller: controller, child: Column( children: <Widget>[ + if (paymentIdValue != null && paymentId != null) + Padding( + padding: EdgeInsets.only(bottom: 32), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: <Widget>[ + Text( + paymentId!, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + fontFamily: 'Lato', + color: Theme.of(context).primaryTextTheme! + .headline6!.color!, + decoration: TextDecoration.none, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + paymentIdValue!, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + fontFamily: 'Lato', + color: Theme.of(context).primaryTextTheme! + .headline6!.color!, + decoration: TextDecoration.none, + ), + ), + ], + ) + ], + ), + ), Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -365,7 +447,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> ) ) ), - if (itemCount > 1) CakeScrollbar( + if (showScrollbar) CakeScrollbar( backgroundHeight: backgroundHeight, thumbHeight: thumbHeight, fromTop: fromTop, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index dc589923f..d5775e2b5 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,11 +1,10 @@ -import 'dart:ui'; +import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/view_model/send/output.dart'; -import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart new file mode 100644 index 000000000..0e56d6e58 --- /dev/null +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -0,0 +1,155 @@ +import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cw_core/node.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +class ConnectionSyncPage extends BasePage { + ConnectionSyncPage(this.nodeListViewModel, this.dashboardViewModel); + + @override + String get title => S.current.connection_sync; + + final NodeListViewModel nodeListViewModel; + final DashboardViewModel dashboardViewModel; + + @override + Widget body(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 10), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SettingsCellWithArrow( + title: S.current.reconnect, + handler: (context) => _presentReconnectAlert(context), + ), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + SettingsCellWithArrow( + title: S.current.rescan, + handler: (context) => Navigator.of(context).pushNamed(Routes.rescan), + ), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + NodeHeaderListRow( + title: S.of(context).add_new_node, + onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode), + ), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + SizedBox(height: 100), + Observer( + builder: (BuildContext context) { + return Flexible( + child: SectionStandardList( + sectionCount: 1, + context: context, + dividerPadding: EdgeInsets.symmetric(horizontal: 24), + itemCounter: (int sectionIndex) { + return nodeListViewModel.nodes.length; + }, + itemBuilder: (_, sectionIndex, index) { + final node = nodeListViewModel.nodes[index]; + final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex; + final nodeListRow = NodeListRow( + title: node.uriRaw, + isSelected: isSelected, + isAlive: node.requestNode(), + onTap: (_) async { + if (isSelected) { + return; + } + + await showPopUp<void>( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).change_current_node_title, + alertContent: S.of(context).change_current_node(node.uriRaw), + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).change, + actionLeftButton: () => Navigator.of(context).pop(), + actionRightButton: () async { + await nodeListViewModel.setAsCurrent(node); + Navigator.of(context).pop(); + }, + ); + }); + }, + ); + + final dismissibleRow = Slidable( + key: Key('${node.keyIndex}'), + startActionPane: _actionPane(context, node), + endActionPane: _actionPane(context, node), + child: nodeListRow, + ); + + return isSelected ? nodeListRow : dismissibleRow; + }, + ), + ); + }, + ), + ], + ), + ); + } + + Future<void> _presentReconnectAlert(BuildContext context) async { + await showPopUp<void>( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).reconnection, + alertContent: S.of(context).reconnect_alert_text, + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + actionRightButton: () async { + Navigator.of(context).pop(); + await dashboardViewModel.reconnect(); + }, + actionLeftButton: () => Navigator.of(context).pop()); + }, + ); + } + + ActionPane _actionPane(BuildContext context, Node node) => ActionPane( + motion: const ScrollMotion(), + extentRatio: 0.3, + children: [ + SlidableAction( + onPressed: (context) async { + final confirmed = await showPopUp<bool>( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).remove_node, + alertContent: S.of(context).remove_node_message, + rightButtonText: S.of(context).remove, + leftButtonText: S.of(context).cancel, + actionRightButton: () => Navigator.pop(context, true), + actionLeftButton: () => Navigator.pop(context, false)); + }) ?? + false; + + if (confirmed) { + await nodeListViewModel.delete(node); + } + }, + backgroundColor: Colors.red, + foregroundColor: Colors.white, + icon: CupertinoIcons.delete, + label: S.of(context).delete, + ), + ], + ); +} diff --git a/lib/src/screens/settings/display_settings_page.dart b/lib/src/screens/settings/display_settings_page.dart new file mode 100644 index 000000000..cd413ff45 --- /dev/null +++ b/lib/src/screens/settings/display_settings_page.dart @@ -0,0 +1,80 @@ +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/entities/language_service.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/themes/theme_list.dart'; +import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; +import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class DisplaySettingsPage extends BasePage { + DisplaySettingsPage(this._displaySettingsViewModel); + + @override + String get title => S.current.display_settings; + + final DisplaySettingsViewModel _displaySettingsViewModel; + + @override + Widget body(BuildContext context) { + return Observer(builder: (_) { + return Container( + padding: EdgeInsets.only(top: 10), + child: Column( + children: [ + SettingsSwitcherCell( + title: S.current.settings_display_balance, + value: _displaySettingsViewModel.shouldDisplayBalance, + onValueChange: (_, bool value) { + _displaySettingsViewModel.setShouldDisplayBalance(value); + }), + if (!isHaven) + SettingsPickerCell<FiatCurrency>( + title: S.current.settings_currency, + searchHintText: S.current.search_currency, + items: FiatCurrency.all, + selectedItem: _displaySettingsViewModel.fiatCurrency, + onItemSelected: (FiatCurrency currency) => _displaySettingsViewModel.setFiatCurrency(currency), + images: FiatCurrency.all.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png")).toList(), + isGridView: true, + matchingCriteria: (FiatCurrency currency, String searchText) { + return currency.title.toLowerCase().contains(searchText) || + currency.fullName.toLowerCase().contains(searchText); + }, + ), + SettingsPickerCell<String>( + title: S.current.settings_change_language, + searchHintText: S.current.search_language, + items: LanguageService.list.keys.toList(), + displayItem: (dynamic code) { + return LanguageService.list[code] ?? ''; + }, + selectedItem: _displaySettingsViewModel.languageCode, + onItemSelected: _displaySettingsViewModel.onLanguageSelected, + images: LanguageService.list.keys + .map((e) => Image.asset("assets/images/flags/${LanguageService.localeCountryCode[e]}.png")) + .toList(), + matchingCriteria: (String code, String searchText) { + return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false; + }, + ), + SettingsChoicesCell( + ChoicesListItem<ThemeBase>( + title: S.current.color_theme, + items: ThemeList.all, + selectedItem: _displaySettingsViewModel.theme, + onItemSelected: (ThemeBase theme) => _displaySettingsViewModel.setTheme(theme), + ), + ), + ], + ), + ); + }); + } +} diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart new file mode 100644 index 000000000..e055e2e8e --- /dev/null +++ b/lib/src/screens/settings/other_settings_page.dart @@ -0,0 +1,45 @@ +import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class OtherSettingsPage extends BasePage { + OtherSettingsPage(this._otherSettingsViewModel); + + @override + String get title => S.current.other_settings; + + final OtherSettingsViewModel _otherSettingsViewModel; + + @override + Widget body(BuildContext context) { + return Observer(builder: (_) { + return Container( + padding: EdgeInsets.only(top: 10), + child: Column(children: [ + SettingsPickerCell( + title: S.current.settings_fee_priority, + items: priorityForWalletType(_otherSettingsViewModel.walletType), + displayItem: _otherSettingsViewModel.getDisplayPriority, + selectedItem: _otherSettingsViewModel.transactionPriority, + onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, + ), + SettingsCellWithArrow( + title: S.current.settings_terms_and_conditions, + handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.readDisclaimer), + ), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + Spacer(), + SettingsVersionCell(title: S.of(context).version(_otherSettingsViewModel.currentVersion)) + ]), + ); + }); + } +} diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart new file mode 100644 index 000000000..128d864f4 --- /dev/null +++ b/lib/src/screens/settings/privacy_page.dart @@ -0,0 +1,43 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; +import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class PrivacyPage extends BasePage { + PrivacyPage(this._privacySettingsViewModel); + + @override + String get title => S.current.privacy_settings; + + final PrivacySettingsViewModel _privacySettingsViewModel; + + @override + Widget body(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 10), + child: Observer(builder: (_) { + return Observer(builder: (_) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SettingsSwitcherCell( + title: S.current.disable_exchange, + value: _privacySettingsViewModel.disableExchange, + onValueChange: (BuildContext context, bool value) { + _privacySettingsViewModel.setEnableExchange(value); + }), + SettingsSwitcherCell( + title: S.current.settings_save_recipient_address, + value: _privacySettingsViewModel.shouldSaveRecipientAddress, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setShouldSaveRecipientAddress(value); + }) + ], + ); + }); + }), + ); + } +} diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart new file mode 100644 index 000000000..224c941ee --- /dev/null +++ b/lib/src/screens/settings/security_backup_page.dart @@ -0,0 +1,84 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class SecurityBackupPage extends BasePage { + SecurityBackupPage(this._securitySettingsViewModel); + + @override + String get title => S.current.security_and_backup; + + final SecuritySettingsViewModel _securitySettingsViewModel; + + @override + Widget body(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 10), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + SettingsCellWithArrow( + title: S.current.show_keys, + handler: (_) => Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { + if (isAuthenticatedSuccessfully) { + auth.close(route: Routes.showKeys); + } + }), + ), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + SettingsCellWithArrow( + title: S.current.create_backup, + handler: (_) => Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { + if (isAuthenticatedSuccessfully) { + auth.close(route: Routes.backup); + } + }), + ), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + SettingsCellWithArrow( + title: S.current.settings_change_pin, + handler: (_) => Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { + auth.close( + route: isAuthenticatedSuccessfully ? Routes.setupPin : null, + arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) { + setupPinContext.close(); + }, + ); + })), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + Observer(builder: (_) { + return SettingsSwitcherCell( + title: S.current.settings_allow_biometrical_authentication, + value: _securitySettingsViewModel.allowBiometricalAuthentication, + onValueChange: (BuildContext context, bool value) { + if (value) { + Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { + if (isAuthenticatedSuccessfully) { + if (await _securitySettingsViewModel.biometricAuthenticated()) { + _securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + } + } else { + _securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + } + + auth.close(); + }); + } else { + _securitySettingsViewModel.setAllowBiometricalAuthentication(value); + } + }); + }), + ]), + ); + } +} diff --git a/lib/src/screens/settings/settings.dart b/lib/src/screens/settings/settings.dart deleted file mode 100644 index 3f6f41e7b..000000000 --- a/lib/src/screens/settings/settings.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart'; -import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart'; -import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; -import 'package:cake_wallet/view_model/settings/version_list_item.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; -import 'package:cake_wallet/view_model/settings/link_list_item.dart'; -import 'package:cake_wallet/view_model/settings/picker_list_item.dart'; -import 'package:cake_wallet/view_model/settings/regular_list_item.dart'; -import 'package:cake_wallet/view_model/settings/switcher_list_item.dart'; -import 'package:cake_wallet/src/screens/settings/widgets/settings_link_provider_cell.dart'; -import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; -import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; -import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; - -class SettingsPage extends BasePage { - SettingsPage(this.settingsViewModel); - - final SettingsViewModel settingsViewModel; - - @override - String get title => S.current.settings_title; - - @override - Widget body(BuildContext context) { - // FIX-ME: Added `context` it was not used here before, maby bug ? - return SectionStandardList( - context: context, - sectionCount: settingsViewModel.sections.length, - itemCounter: (int sectionIndex) { - if (sectionIndex < settingsViewModel.sections.length) { - return settingsViewModel.sections[sectionIndex].length; - } - - return 0; - }, - itemBuilder: (_, sectionIndex, itemIndex) { - final item = settingsViewModel.sections[sectionIndex][itemIndex]; - - if (item is PickerListItem) { - return Observer(builder: (_) { - return SettingsPickerCell<Object>( - displayItem: item.displayItem, - title: item.title, - selectedItem: item.selectedItem(), - items: item.items, - onItemSelected: (dynamic value) => item.onItemSelected(value), - images: item.images, - searchHintText: item.searchHintText, - isGridView: item.isGridView, - matchingCriteria: (dynamic value, String searchText) => item.matchingCriteria(value, searchText), - ); - }); - } - - if (item is SwitcherListItem) { - return Observer(builder: (_) { - return SettingsSwitcherCell( - title: item.title, - value: item.value(), - onValueChange: item.onValueChange); - }); - } - - if (item is RegularListItem) { - return SettingsCellWithArrow( - title: item.title, handler: item.handler); - } - - if (item is LinkListItem) { - return SettingsLinkProviderCell( - title: item.title, - icon: item.icon, - link: item.link, - linkTitle: item.linkTitle); - } - - if (item is VersionListItem) { - return Observer(builder: (_) { - return SettingsVersionCell( - title: - S.of(context).version(settingsViewModel.currentVersion)); - }); - } - - if (item is ChoicesListItem) { - return SettingsChoicesCell(item); - } - - return Container(); - }); - } -} diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 7713284f2..70370e227 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -11,6 +11,10 @@ class BaseAlertDialog extends StatelessWidget { VoidCallback get actionLeft => () {}; VoidCallback get actionRight => () {}; bool get barrierDismissible => true; + Color? get leftActionButtonTextColor => null; + Color? get rightActionButtonTextColor => null; + Color? get leftActionButtonColor => null; + Color? get rightActionButtonColor => null; Widget title(BuildContext context) { return Text( @@ -45,52 +49,64 @@ class BaseAlertDialog extends StatelessWidget { height: 52, child: Row( mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ - Flexible( - child: Container( - width: double.infinity, - color: Theme.of(context).accentTextTheme!.bodyText1!.decorationColor!, - child: TextButton( - onPressed: actionLeft, - child: Text( - leftActionButtonText, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme!.bodyText1!.backgroundColor!, - decoration: TextDecoration.none, - ), - )), - ), - ), + Expanded( + child: TextButton( + onPressed: actionLeft, + style: TextButton.styleFrom( + backgroundColor: leftActionButtonColor ?? + Theme.of(context) + .accentTextTheme! + .bodyText1! + .decorationColor!, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero))), + child: Text( + leftActionButtonText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: leftActionButtonTextColor ?? + Theme.of(context).primaryTextTheme! + .bodyText1!.backgroundColor!, + decoration: TextDecoration.none, + ), + )), + ), Container( width: 1, color: Theme.of(context).dividerColor, ), - Flexible( - child: Container( - width: double.infinity, - color: Theme.of(context).accentTextTheme!.bodyText2!.backgroundColor!, - child: TextButton( - onPressed: actionRight, - child: Text( - rightActionButtonText, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).primaryTextTheme!.bodyText2!.backgroundColor!, - decoration: TextDecoration.none, - ), - )), - ), - ), - ], - )); + Expanded( + child: TextButton( + onPressed: actionRight, + style: TextButton.styleFrom( + backgroundColor: rightActionButtonColor ?? + Theme.of(context).accentTextTheme! + .bodyText2!.backgroundColor!, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero))), + child: Text( + rightActionButtonText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: rightActionButtonTextColor ?? + Theme.of(context) + .primaryTextTheme! + .bodyText2! + .backgroundColor!, + decoration: TextDecoration.none, + ), + )), + ), + ], + )); } @override diff --git a/lib/src/widgets/standard_list.dart b/lib/src/widgets/standard_list.dart index ae8978eb4..f5abdd900 100644 --- a/lib/src/widgets/standard_list.dart +++ b/lib/src/widgets/standard_list.dart @@ -123,6 +123,7 @@ class SectionStandardList extends StatelessWidget { required this.itemBuilder, required this.sectionCount, required BuildContext context, + this.dividerPadding = const EdgeInsets.only(left: 24), this.themeColor, this.dividerThemeColor, this.sectionTitleBuilder, @@ -149,6 +150,7 @@ class SectionStandardList extends StatelessWidget { final List<Widget> totalRows; final Color? themeColor; final Color? dividerThemeColor; + final EdgeInsets dividerPadding; List<Widget> transform( bool hasTopSeparator, @@ -178,7 +180,7 @@ class SectionStandardList extends StatelessWidget { items.add(sectionIndex + 1 != sectionCount ? SectionHeaderListRow() - : StandardListSeparator(padding: EdgeInsets.only(left: 24))); + : StandardListSeparator(padding: dividerPadding)); } return items; diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 2b3105a34..534ea67e9 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -30,6 +30,7 @@ abstract class SettingsStoreBase with Store { required BalanceDisplayMode initialBalanceDisplayMode, required bool initialSaveRecipientAddress, required bool initialAllowBiometricalAuthentication, + required bool initialExchangeEnabled, required ThemeBase initialTheme, required int initialPinLength, required String initialLanguageCode, @@ -47,6 +48,7 @@ abstract class SettingsStoreBase with Store { balanceDisplayMode = initialBalanceDisplayMode, shouldSaveRecipientAddress = initialSaveRecipientAddress, allowBiometricalAuthentication = initialAllowBiometricalAuthentication, + disableExchange = initialExchangeEnabled, currentTheme = initialTheme, pinCodeLength = initialPinLength, languageCode = initialLanguageCode, @@ -143,6 +145,9 @@ abstract class SettingsStoreBase with Store { @observable bool allowBiometricalAuthentication; + @observable + bool disableExchange; + @observable ThemeBase currentTheme; @@ -221,6 +226,8 @@ abstract class SettingsStoreBase with Store { final allowBiometricalAuthentication = sharedPreferences .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false; + final disableExchange = sharedPreferences + .getBool(PreferencesKey.disableExchangeKey) ?? false; final legacyTheme = (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false) ? ThemeType.dark.index @@ -284,6 +291,7 @@ abstract class SettingsStoreBase with Store { initialBalanceDisplayMode: currentBalanceDisplayMode, initialSaveRecipientAddress: shouldSaveRecipientAddress, initialAllowBiometricalAuthentication: allowBiometricalAuthentication, + initialExchangeEnabled: disableExchange, initialTheme: savedTheme, actionlistDisplayMode: actionListDisplayMode, initialPinLength: pinLength, diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 49dd2437a..7f11b0c2f 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -53,7 +53,6 @@ abstract class DashboardViewModelBase with Store { hasBuyAction = false, isEnabledBuyAction = false, hasExchangeAction = false, - isEnabledExchangeAction = false, isShowFirstYatIntroduction = false, isShowSecondYatIntroduction = false, isShowThirdYatIntroduction = false, @@ -249,8 +248,8 @@ abstract class DashboardViewModelBase with Store { void furtherShowYatPopup(bool shouldShow) => settingsStore.shouldShowYatPopup = shouldShow; - @observable - bool isEnabledExchangeAction; + @computed + bool get isEnabledExchangeAction => !settingsStore.disableExchange; @observable bool hasExchangeAction; @@ -365,7 +364,6 @@ abstract class DashboardViewModelBase with Store { } void updateActions() { - isEnabledExchangeAction = true; hasExchangeAction = !isHaven; isEnabledBuyAction = wallet.type != WalletType.haven && wallet.type != WalletType.monero; diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index e7e73855b..b19434166 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -6,9 +6,8 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; -import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; -import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart'; +import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/sync_status.dart'; @@ -43,7 +42,7 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel; abstract class ExchangeViewModelBase with Store { ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore, - this.tradesStore, this._settingsStore, this.sharedPreferences, this._settingsViewModel) + this.tradesStore, this._settingsStore, this.sharedPreferences) : _cryptoNumberFormat = NumberFormat(), isReverse = false, isFixedRateMode = false, @@ -190,6 +189,19 @@ abstract class ExchangeViewModelBase with Store { ObservableList<ExchangeTemplate> get templates => _exchangeTemplateStore.templates; + + @computed + TransactionPriority get transactionPriority { + final priority = _settingsStore.priority[wallet.type]; + + if (priority == null) { + throw Exception('Unexpected type ${wallet.type.toString()}'); + } + + return priority; + } + + bool get hasAllAmount => wallet.type == WalletType.bitcoin && depositCurrency == wallet.currency; @@ -199,11 +211,11 @@ abstract class ExchangeViewModelBase with Store { switch (wallet.type) { case WalletType.monero: case WalletType.haven: - return _settingsViewModel.transactionPriority == monero!.getMoneroTransactionPrioritySlow(); + return transactionPriority == monero!.getMoneroTransactionPrioritySlow(); case WalletType.bitcoin: - return _settingsViewModel.transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow(); + return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow(); case WalletType.litecoin: - return _settingsViewModel.transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow(); + return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow(); default: return false; } @@ -221,8 +233,6 @@ abstract class ExchangeViewModelBase with Store { final SettingsStore _settingsStore; - final SettingsViewModel _settingsViewModel; - double _bestRate = 0.0; late Timer bestRateSync; @@ -297,12 +307,14 @@ abstract class ExchangeViewModelBase with Store { } Future<void> _calculateBestRate() async { + final amount = double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1; + final result = await Future.wait<double>( _tradeAvailableProviders .map((element) => element.calculateAmount( from: depositCurrency, to: receiveCurrency, - amount: 1, + amount: amount, isFixedRateMode: isFixedRateMode, isReceiveAmount: false)) ); @@ -312,7 +324,7 @@ abstract class ExchangeViewModelBase with Store { for (int i=0;i<result.length;i++) { if (result[i] != 0) { /// add this provider as its valid for this trade - _sortedAvailableProviders[result[i]] = _tradeAvailableProviders[i]; + _sortedAvailableProviders[result[i] / amount] = _tradeAvailableProviders[i]; } } if (_sortedAvailableProviders.isNotEmpty) { @@ -335,7 +347,7 @@ abstract class ExchangeViewModelBase with Store { ? depositCurrency : receiveCurrency; - double lowestMin = double.maxFinite; + double? lowestMin = double.maxFinite; double? highestMax = 0.0; for (var provider in selectedProviders) { @@ -350,8 +362,8 @@ abstract class ExchangeViewModelBase with Store { to: to, isFixedRateMode: isFixedRateMode); - if (tempLimits.min != null && tempLimits.min! < lowestMin) { - lowestMin = tempLimits.min!; + if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) { + lowestMin = tempLimits.min; } if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) { highestMax = tempLimits.max; @@ -361,7 +373,7 @@ abstract class ExchangeViewModelBase with Store { } } - if (lowestMin < double.maxFinite) { + if (lowestMin != double.maxFinite) { limits = Limits(min: lowestMin, max: highestMax); limitsState = LimitsLoadedSuccessfully(limits: limits); @@ -375,96 +387,105 @@ abstract class ExchangeViewModelBase with Store { TradeRequest? request; String amount = ''; - for (var provider in _sortedAvailableProviders.values) { - if (!(await provider.checkIsAvailable())) { - continue; - } + try { + for (var provider in _sortedAvailableProviders.values) { + if (!(await provider.checkIsAvailable())) { + continue; + } - if (provider is SideShiftExchangeProvider) { - request = SideShiftRequest( - depositMethod: depositCurrency, - settleMethod: receiveCurrency, - depositAmount: depositAmount.replaceAll(',', '.'), - settleAddress: receiveAddress, - refundAddress: depositAddress, - ); - amount = depositAmount; - } + if (provider is SideShiftExchangeProvider) { + request = SideShiftRequest( + depositMethod: depositCurrency, + settleMethod: receiveCurrency, + depositAmount: depositAmount.replaceAll(',', '.'), + settleAddress: receiveAddress, + refundAddress: depositAddress, + ); + amount = depositAmount; + } - if (provider is SimpleSwapExchangeProvider) { - request = SimpleSwapRequest( - from: depositCurrency, - to: receiveCurrency, - amount: depositAmount.replaceAll(',', '.'), - address: receiveAddress, - refundAddress: depositAddress, - ); - amount = depositAmount; - } - - if (provider is XMRTOExchangeProvider) { - request = XMRTOTradeRequest( + if (provider is SimpleSwapExchangeProvider) { + request = SimpleSwapRequest( from: depositCurrency, to: receiveCurrency, amount: depositAmount.replaceAll(',', '.'), - receiveAmount: receiveAmount.replaceAll(',', '.'), address: receiveAddress, refundAddress: depositAddress, - isBTCRequest: isReceiveAmountEntered); - amount = depositAmount; - } + ); + amount = depositAmount; + } - if (provider is ChangeNowExchangeProvider) { - request = ChangeNowRequest( - from: depositCurrency, - to: receiveCurrency, - fromAmount: depositAmount.replaceAll(',', '.'), - toAmount: receiveAmount.replaceAll(',', '.'), - refundAddress: depositAddress, - address: receiveAddress, - isReverse: isReverse); - amount = isReverse ? receiveAmount : depositAmount; - } + if (provider is XMRTOExchangeProvider) { + request = XMRTOTradeRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount.replaceAll(',', '.'), + receiveAmount: receiveAmount.replaceAll(',', '.'), + address: receiveAddress, + refundAddress: depositAddress, + isBTCRequest: isReceiveAmountEntered); + amount = depositAmount; + } - if (provider is MorphTokenExchangeProvider) { - request = MorphTokenRequest( - from: depositCurrency, - to: receiveCurrency, - amount: depositAmount.replaceAll(',', '.'), - refundAddress: depositAddress, - address: receiveAddress); - amount = depositAmount; - } + if (provider is ChangeNowExchangeProvider) { + request = ChangeNowRequest( + from: depositCurrency, + to: receiveCurrency, + fromAmount: depositAmount.replaceAll(',', '.'), + toAmount: receiveAmount.replaceAll(',', '.'), + refundAddress: depositAddress, + address: receiveAddress, + isReverse: isReverse); + amount = isReverse ? receiveAmount : depositAmount; + } - amount = amount.replaceAll(',', '.'); + if (provider is MorphTokenExchangeProvider) { + request = MorphTokenRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount.replaceAll(',', '.'), + refundAddress: depositAddress, + address: receiveAddress); + amount = depositAmount; + } - if (limitsState is LimitsLoadedSuccessfully) { - if (double.parse(amount) < limits.min!) { - continue; - } else if (limits.max != null && double.parse(amount) > limits.max!) { - continue; - } else { - try { - tradeState = TradeIsCreating(); - final trade = await provider.createTrade( - request: request!, isFixedRateMode: isFixedRateMode); - trade.walletId = wallet.id; - tradesStore.setTrade(trade); - await trades.add(trade); - tradeState = TradeIsCreatedSuccessfully(trade: trade); - /// return after the first successful trade - return; - } catch (e) { + amount = amount.replaceAll(',', '.'); + + if (limitsState is LimitsLoadedSuccessfully) { + if (double.parse(amount) < limits.min!) { continue; + } else if (limits.max != null && double.parse(amount) > limits.max!) { + continue; + } else { + try { + tradeState = TradeIsCreating(); + final trade = await provider.createTrade( + request: request!, isFixedRateMode: isFixedRateMode); + trade.walletId = wallet.id; + tradesStore.setTrade(trade); + await trades.add(trade); + tradeState = TradeIsCreatedSuccessfully(trade: trade); + /// return after the first successful trade + return; + } catch (e) { + continue; + } } } } - } - /// if the code reached here then none of the providers succeeded - tradeState = TradeIsCreatedFailure( - title: S.current.trade_not_created, - error: S.current.none_of_selected_providers_can_exchange); + /// if the code reached here then none of the providers succeeded + tradeState = TradeIsCreatedFailure( + title: S.current.trade_not_created, + error: S.current.none_of_selected_providers_can_exchange); + } on ConcurrentModificationError { + /// if create trade happened at the exact same time of the scheduled rate update + /// then delay the create trade a bit and try again + /// + /// this is because the limitation of the SplayTreeMap that + /// you can't modify it while iterating through it + Future.delayed(Duration(milliseconds: 500), createTrade); + } } @action diff --git a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart index da71f31fa..df6a23718 100644 --- a/lib/view_model/ionia/ionia_purchase_merch_view_model.dart +++ b/lib/view_model/ionia/ionia_purchase_merch_view_model.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; +import 'package:cake_wallet/view_model/send/send_view_model.dart'; part 'ionia_purchase_merch_view_model.g.dart'; @@ -17,6 +18,7 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { required this.ioniaAnyPayService, required this.amount, required this.ioniaMerchant, + required this.sendViewModel, }) : tipAmount = 0.0, percentage = 0.0, invoiceCreationState = InitialExecutionState(), @@ -40,6 +42,8 @@ abstract class IoniaMerchPurchaseViewModelBase with Store { final IoniaMerchant ioniaMerchant; + final SendViewModel sendViewModel; + final IoniaAnyPay ioniaAnyPayService; IoniaAnyPayPaymentInfo? paymentInfo; diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 34167ae0a..befb38d27 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -14,11 +14,11 @@ abstract class NodeCreateOrEditViewModelBase with Store { : state = InitialExecutionState(), connectionState = InitialExecutionState(), useSSL = false, - trusted = false, address = '', port = '', login = '', - password = ''; + password = '', + trusted = false; @observable ExecutionState state; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index cdef6faba..25a5cc7ee 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,10 +1,10 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; +import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; -import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/template.dart'; @@ -42,7 +42,8 @@ abstract class SendViewModelBase with Store { : state = InitialExecutionState(), currencies = _wallet.balance.keys.toList(), selectedCryptoCurrency = _wallet.currency, - outputs = ObservableList<Output>() { + outputs = ObservableList<Output>(), + fiatFromSettings = _settingsStore.fiatCurrency { final priority = _settingsStore.priority[_wallet.type]; final priorities = priorityForWalletType(_wallet.type); @@ -52,7 +53,7 @@ abstract class SendViewModelBase with Store { outputs.add(Output(_wallet, _settingsStore, _fiatConversationStore, () => selectedCryptoCurrency)); } - + @observable ExecutionState state; @@ -133,11 +134,13 @@ abstract class SendViewModelBase with Store { Validator get textValidator => TextValidator(); + final FiatCurrency fiatFromSettings; + @observable PendingTransaction? pendingTransaction; @computed - String get balance => balanceViewModel.availableBalance ?? '0.0'; + String get balance => balanceViewModel.availableBalance; @computed bool get isReadyForSend => _wallet.syncStatus is SyncedSyncStatus; @@ -166,6 +169,9 @@ abstract class SendViewModelBase with Store { bool get hasCurrecyChanger => walletType == WalletType.haven; + @computed + FiatCurrency get fiatCurrency => _settingsStore.fiatCurrency; + final WalletBase _wallet; final SettingsStore _settingsStore; final SendTemplateViewModel sendTemplateViewModel; @@ -208,7 +214,7 @@ abstract class SendViewModelBase with Store { state = TransactionCommitting(); await pendingTransaction!.commit(); - if (pendingTransaction!.id?.isNotEmpty ?? false) { + if (pendingTransaction!.id.isNotEmpty) { _settingsStore.shouldSaveRecipientAddress ? await transactionDescriptionBox.add(TransactionDescription( id: pendingTransaction!.id, @@ -283,4 +289,12 @@ abstract class SendViewModelBase with Store { bool _isEqualCurrency(String currency) => currency.toLowerCase() == _wallet.currency.title.toLowerCase(); + + @action + void onClose() => + _settingsStore.fiatCurrency = fiatFromSettings; + + @action + void setFiatCurrency(FiatCurrency fiat) => + _settingsStore.fiatCurrency = fiat; } diff --git a/lib/view_model/settings/display_settings_view_model.dart b/lib/view_model/settings/display_settings_view_model.dart new file mode 100644 index 000000000..4e73b3399 --- /dev/null +++ b/lib/view_model/settings/display_settings_view_model.dart @@ -0,0 +1,57 @@ +import 'package:cake_wallet/entities/balance_display_mode.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:mobx/mobx.dart'; + +part 'display_settings_view_model.g.dart'; + +class DisplaySettingsViewModel = DisplaySettingsViewModelBase with _$DisplaySettingsViewModel; + +abstract class DisplaySettingsViewModelBase with Store { + DisplaySettingsViewModelBase( + this._settingsStore, + ); + + final SettingsStore _settingsStore; + + @computed + FiatCurrency get fiatCurrency => _settingsStore.fiatCurrency; + + @computed + String get languageCode => _settingsStore.languageCode; + + @computed + BalanceDisplayMode get balanceDisplayMode => _settingsStore.balanceDisplayMode; + + @computed + bool get shouldDisplayBalance => balanceDisplayMode == BalanceDisplayMode.displayableBalance; + + @computed + ThemeBase get theme => _settingsStore.currentTheme; + + @action + void setBalanceDisplayMode(BalanceDisplayMode value) => _settingsStore.balanceDisplayMode = value; + + @action + void setShouldDisplayBalance(bool value) { + if (value) { + _settingsStore.balanceDisplayMode = BalanceDisplayMode.displayableBalance; + } else { + _settingsStore.balanceDisplayMode = BalanceDisplayMode.hiddenBalance; + } + } + + @action + void onLanguageSelected(String code) { + _settingsStore.languageCode = code; + } + + @action + void setTheme(ThemeBase newTheme) { + _settingsStore.currentTheme = newTheme; + } + + @action + void setFiatCurrency(FiatCurrency value) => _settingsStore.fiatCurrency = value; +} diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart new file mode 100644 index 000000000..c57af50d9 --- /dev/null +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -0,0 +1,64 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/balance.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:mobx/mobx.dart'; +import 'package:package_info/package_info.dart'; + +part 'other_settings_view_model.g.dart'; + +class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel; + +abstract class OtherSettingsViewModelBase with Store { + OtherSettingsViewModelBase(this._settingsStore, this._wallet) + : walletType = _wallet.type, + currentVersion = '' { + PackageInfo.fromPlatform() + .then((PackageInfo packageInfo) => currentVersion = packageInfo.version); + + final priority = _settingsStore.priority[_wallet.type]; + final priorities = priorityForWalletType(_wallet.type); + + if (!priorities.contains(priority)) { + _settingsStore.priority[_wallet.type] = priorities.first; + } + } + + final WalletType walletType; + final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet; + + @observable + String currentVersion; + + final SettingsStore _settingsStore; + + @computed + TransactionPriority get transactionPriority { + final priority = _settingsStore.priority[walletType]; + + if (priority == null) { + throw Exception('Unexpected type ${walletType.toString()}'); + } + + return priority; + } + + String getDisplayPriority(dynamic priority) { + final _priority = priority as TransactionPriority; + + if (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin) { + final rate = bitcoin!.getFeeRate(_wallet, _priority); + return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); + } + + return priority.toString(); + } + + void onDisplayPrioritySelected(TransactionPriority priority) => + _settingsStore.priority[_wallet.type] = priority; +} diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart new file mode 100644 index 000000000..88311bf1f --- /dev/null +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -0,0 +1,24 @@ +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:mobx/mobx.dart'; + +part 'privacy_settings_view_model.g.dart'; + +class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel; + +abstract class PrivacySettingsViewModelBase with Store { + PrivacySettingsViewModelBase(this._settingsStore); + + final SettingsStore _settingsStore; + + @computed + bool get disableExchange => _settingsStore.disableExchange; + + @computed + bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress; + + @action + void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value; + + @action + void setEnableExchange(bool value) => _settingsStore.disableExchange = value; +} diff --git a/lib/view_model/settings/security_settings_view_model.dart b/lib/view_model/settings/security_settings_view_model.dart new file mode 100644 index 000000000..4a88b633f --- /dev/null +++ b/lib/view_model/settings/security_settings_view_model.dart @@ -0,0 +1,25 @@ +import 'package:cake_wallet/entities/biometric_auth.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:mobx/mobx.dart'; + +part 'security_settings_view_model.g.dart'; + +class SecuritySettingsViewModel = SecuritySettingsViewModelBase with _$SecuritySettingsViewModel; + +abstract class SecuritySettingsViewModelBase with Store { + SecuritySettingsViewModelBase(this._settingsStore) : _biometricAuth = BiometricAuth(); + + final BiometricAuth _biometricAuth; + final SettingsStore _settingsStore; + + @computed + bool get allowBiometricalAuthentication => _settingsStore.allowBiometricalAuthentication; + + @action + Future<bool> biometricAuthenticated() async { + return await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated(); + } + + @action + void setAllowBiometricalAuthentication(bool value) => _settingsStore.allowBiometricalAuthentication = value; +} diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart deleted file mode 100644 index aabe51ef1..000000000 --- a/lib/view_model/settings/settings_view_model.dart +++ /dev/null @@ -1,336 +0,0 @@ -import 'package:cake_wallet/entities/language_service.dart'; -import 'package:cake_wallet/store/yat/yat_store.dart'; -import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:mobx/mobx.dart'; -import 'package:package_info/package_info.dart'; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/entities/biometric_auth.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/entities/balance_display_mode.dart'; -import 'package:cake_wallet/entities/fiat_currency.dart'; -import 'package:cw_core/node.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/haven/haven.dart'; -import 'package:cake_wallet/entities/action_list_display_mode.dart'; -import 'package:cake_wallet/view_model/settings/version_list_item.dart'; -import 'package:cake_wallet/view_model/settings/picker_list_item.dart'; -import 'package:cake_wallet/view_model/settings/regular_list_item.dart'; -import 'package:cake_wallet/view_model/settings/settings_list_item.dart'; -import 'package:cake_wallet/view_model/settings/switcher_list_item.dart'; -import 'package:cake_wallet/src/screens/auth/auth_page.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cw_core/transaction_history.dart'; -import 'package:cw_core/balance.dart'; -import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/transaction_priority.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:cake_wallet/themes/theme_list.dart'; -import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; - -part 'settings_view_model.g.dart'; - -class SettingsViewModel = SettingsViewModelBase with _$SettingsViewModel; - -List<TransactionPriority> priorityForWalletType(WalletType type) { - switch (type) { - case WalletType.monero: - return monero!.getTransactionPriorities(); - case WalletType.bitcoin: - return bitcoin!.getTransactionPriorities(); - case WalletType.litecoin: - return bitcoin!.getLitecoinTransactionPriorities(); - case WalletType.haven: - return haven!.getTransactionPriorities(); - default: - return []; - } -} - -abstract class SettingsViewModelBase with Store { - SettingsViewModelBase( - this._settingsStore, - this._yatStore, - WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, - TransactionInfo> - wallet) - : itemHeaders = {}, - _walletType = wallet.type, - _biometricAuth = BiometricAuth(), - sections = <List<SettingsListItem>>[], - currentVersion = '' { - PackageInfo.fromPlatform().then( - (PackageInfo packageInfo) => currentVersion = packageInfo.version); - - final priority = _settingsStore.priority[wallet.type]; - final priorities = priorityForWalletType(wallet.type); - - if (!priorities.contains(priority)) { - _settingsStore.priority[wallet.type] = priorities.first; - } - - //var connectYatUrl = YatLink.baseUrl + YatLink.signInSuffix; - //final connectYatUrlParameters = - // _yatStore.defineQueryParameters(); - - //if (connectYatUrlParameters.isNotEmpty) { - // connectYatUrl += YatLink.queryParameter + connectYatUrlParameters; - //} - - //var manageYatUrl = YatLink.baseUrl + YatLink.managePath; - //final manageYatUrlParameters = - // _yatStore.defineQueryParameters(); - - //if (manageYatUrlParameters.isNotEmpty) { - // manageYatUrl += YatLink.queryParameter + manageYatUrlParameters; - //} - - //var createNewYatUrl = YatLink.startFlowUrl; - //final createNewYatUrlParameters = - // _yatStore.defineQueryParameters(); - - //if (createNewYatUrlParameters.isNotEmpty) { - // createNewYatUrl += '?sub1=' + createNewYatUrlParameters; - //} - - - sections = [ - [ - SwitcherListItem( - title: S.current.settings_display_balance, - value: () => balanceDisplayMode == BalanceDisplayMode.displayableBalance, - onValueChange: (_, bool value) { - if (value) { - _settingsStore.balanceDisplayMode = BalanceDisplayMode.displayableBalance; - } else { - _settingsStore.balanceDisplayMode = BalanceDisplayMode.hiddenBalance; - } - }, - ), - if (!isHaven) - PickerListItem( - title: S.current.settings_currency, - searchHintText: S.current.search_currency, - items: FiatCurrency.all, - selectedItem: () => fiatCurrency, - onItemSelected: (FiatCurrency currency) => - setFiatCurrency(currency), - images: FiatCurrency.all.map( - (e) => Image.asset("assets/images/flags/${e.countryCode}.png")) - .toList(), - isGridView: true, - matchingCriteria: (FiatCurrency currency, String searchText) { - return currency.title.toLowerCase().contains(searchText) || currency.fullName.toLowerCase().contains(searchText); - }, - ), - PickerListItem( - title: S.current.settings_fee_priority, - items: priorityForWalletType(wallet.type), - displayItem: (dynamic priority) { - final _priority = priority as TransactionPriority; - - if (wallet.type == WalletType.bitcoin - || wallet.type == WalletType.litecoin) { - final rate = bitcoin!.getFeeRate(wallet, _priority); - return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); - } - - return priority.toString(); - }, - selectedItem: () => transactionPriority, - onItemSelected: (TransactionPriority priority) => - _settingsStore.priority[wallet.type] = priority), - SwitcherListItem( - title: S.current.settings_save_recipient_address, - value: () => shouldSaveRecipientAddress, - onValueChange: (_, bool value) => - setShouldSaveRecipientAddress(value)) - ], - [ - RegularListItem( - title: S.current.settings_change_pin, - handler: (BuildContext context) { - Navigator.of(context).pushNamed(Routes.auth, arguments: - (bool isAuthenticatedSuccessfully, AuthPageState auth) { - auth.close( - route: isAuthenticatedSuccessfully ? Routes.setupPin : null, - arguments: (PinCodeState<PinCodeWidget> setupPinContext, - String _) { - setupPinContext.close(); - }, - ); - }); - }), - PickerListItem( - title: S.current.settings_change_language, - searchHintText: S.current.search_language, - items: LanguageService.list.keys.toList(), - displayItem: (dynamic code) { - return LanguageService.list[code] ?? ''; - }, - selectedItem: () => _settingsStore.languageCode, - onItemSelected: (String code) { - _settingsStore.languageCode = code; - }, - images: LanguageService.list.keys.map( - (e) => Image.asset("assets/images/flags/${LanguageService.localeCountryCode[e]}.png")) - .toList(), - matchingCriteria: (String code, String searchText) { - return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false; - }, - ), - SwitcherListItem( - title: S.current.settings_allow_biometrical_authentication, - value: () => allowBiometricalAuthentication, - onValueChange: (BuildContext context, bool value) { - if (value) { - Navigator.of(context).pushNamed(Routes.auth, arguments: - (bool isAuthenticatedSuccessfully, - AuthPageState auth) async { - if (isAuthenticatedSuccessfully) { - if (await _biometricAuth.canCheckBiometrics() && - await _biometricAuth.isAuthenticated()) { - setAllowBiometricalAuthentication( - isAuthenticatedSuccessfully); - } - } else { - setAllowBiometricalAuthentication( - isAuthenticatedSuccessfully); - } - - auth.close(); - }); - } else { - setAllowBiometricalAuthentication(value); - } - }), - ChoicesListItem( - title: S.current.color_theme, - items: ThemeList.all, - selectedItem: theme, - onItemSelected: (ThemeBase theme) => _settingsStore.currentTheme = theme, - ), - ], - //[ - //if (_yatStore.emoji.isNotEmpty) ...[ - // LinkListItem( - // title: S.current.manage_yats, - // link: manageYatUrl, - // linkTitle: ''), - //] else ...[ - //LinkListItem( - // title: S.current.connect_yats, - // link: connectYatUrl, - // linkTitle: ''), - //LinkListItem( - // title: 'Create new Yats', - // link: createNewYatUrl, - // linkTitle: '') - //] - //], - [ - RegularListItem( - title: S.current.settings_terms_and_conditions, - handler: (BuildContext context) => - Navigator.of(context).pushNamed(Routes.readDisclaimer), - ) - ], - [VersionListItem(title: currentVersion)] - ]; - } - - @observable - String currentVersion; - - @computed - Node get node => _settingsStore.getCurrentNode(_walletType); - - @computed - FiatCurrency get fiatCurrency => _settingsStore.fiatCurrency; - - @computed - ObservableList<ActionListDisplayMode> get actionlistDisplayMode => - _settingsStore.actionlistDisplayMode; - - @computed - TransactionPriority get transactionPriority { - final priority = _settingsStore.priority[_walletType]; - - if (priority == null) { - throw Exception('Unexpected type ${_walletType.toString()}'); - } - - return priority; - } - - @computed - BalanceDisplayMode get balanceDisplayMode => - _settingsStore.balanceDisplayMode; - - @computed - bool get shouldSaveRecipientAddress => - _settingsStore.shouldSaveRecipientAddress; - - @computed - bool get allowBiometricalAuthentication => - _settingsStore.allowBiometricalAuthentication; - - @computed - ThemeBase get theme => _settingsStore.currentTheme; - - bool get isBitcoinBuyEnabled => _settingsStore.isBitcoinBuyEnabled; - - final Map<String, String> itemHeaders; - List<List<SettingsListItem>> sections; - final SettingsStore _settingsStore; - final YatStore _yatStore; - final WalletType _walletType; - final BiometricAuth _biometricAuth; - - @action - void setBalanceDisplayMode(BalanceDisplayMode value) => - _settingsStore.balanceDisplayMode = value; - - @action - void setFiatCurrency(FiatCurrency value) => - _settingsStore.fiatCurrency = value; - - @action - void setShouldSaveRecipientAddress(bool value) => - _settingsStore.shouldSaveRecipientAddress = value; - - @action - void setAllowBiometricalAuthentication(bool value) => - _settingsStore.allowBiometricalAuthentication = value; - - @action - void toggleTransactionsDisplay() => - actionlistDisplayMode.contains(ActionListDisplayMode.transactions) - ? _hideTransaction() - : _showTransaction(); - - @action - void toggleTradesDisplay() => - actionlistDisplayMode.contains(ActionListDisplayMode.trades) - ? _hideTrades() - : _showTrades(); - - @action - void _hideTransaction() => - actionlistDisplayMode.remove(ActionListDisplayMode.transactions); - - @action - void _hideTrades() => - actionlistDisplayMode.remove(ActionListDisplayMode.trades); - - @action - void _showTransaction() => - actionlistDisplayMode.add(ActionListDisplayMode.transactions); - - @action - void _showTrades() => actionlistDisplayMode.add(ActionListDisplayMode.trades); -} diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 8992071fd..f8729715b 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -184,7 +184,7 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.monero: return 'https://monero.com/tx/${txId}'; case WalletType.bitcoin: - return 'https://www.blockchain.com/btc/tx/${txId}'; + return 'https://mempool.space/tx/${txId}'; case WalletType.litecoin: return 'https://blockchair.com/litecoin/transaction/${txId}'; case WalletType.haven: @@ -199,7 +199,7 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.monero: return S.current.view_transaction_on + 'Monero.com'; case WalletType.bitcoin: - return S.current.view_transaction_on + 'Blockchain.com'; + return S.current.view_transaction_on + 'mempool.space'; case WalletType.litecoin: return S.current.view_transaction_on + 'Blockchair.com'; case WalletType.haven: diff --git a/model_generator.sh b/model_generator.sh new file mode 100644 index 000000000..f4ef8bdad --- /dev/null +++ b/model_generator.sh @@ -0,0 +1,5 @@ +cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +flutter packages pub run build_runner build --delete-conflicting-outputs \ No newline at end of file diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 248a06de0..00e1a39b1 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -34,7 +34,7 @@ dependencies: local_auth: ^2.1.0 package_info: ^2.0.0 #package_info_plus: ^1.4.2 - devicelocale: ^0.5.4 + devicelocale: ^0.4.3 auto_size_text: ^3.0.0 dotted_border: ^2.0.0+2 smooth_page_indicator: ^1.0.0+2 diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 5bddea801..8f339d090 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "GETRENNT", "sync_status_connecting" : "VERBINDEN", "sync_status_connected" : "VERBUNDEN", + "sync_status_attempting_sync" : "SYNC VERSUCHEN", "transaction_priority_slow" : "Langsam", @@ -653,7 +654,14 @@ "use_suggested": "Vorgeschlagen verwenden", "do_not_share_warning_text" : "Teilen Sie diese nicht mit anderen, einschließlich des Supports.\n\nSie werden Ihr Geld stehlen!", "help": "hilfe", - "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", + "connection_sync": "Verbindung und Synchronisierung", + "security_and_backup": "Sicherheit und Datensicherung", + "create_backup": "Backup erstellen", "privacy_settings": "Datenschutzeinstellungen", + "privacy": "Datenschutz", + "display_settings": "Anzeigeeinstellungen", + "other_settings": "Andere Einstellungen", + "disable_exchange": "Exchange deaktivieren", + "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", "settings_can_be_changed_later": "Diese Einstellungen können später in den App-Einstellungen geändert werden" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 4ff6c2047..b50ba6c83 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "DISCONNECTED", "sync_status_connecting" : "CONNECTING", "sync_status_connected" : "CONNECTED", + "sync_status_attempting_sync" : "ATTEMPTING SYNC", "transaction_priority_slow" : "Slow", @@ -653,7 +654,14 @@ "use_suggested": "Use Suggested", "do_not_share_warning_text" : "Do not share these with anyone else, including support.\n\nThey will steal your money!", "help": "help", + "connection_sync": "Connection and sync", + "security_and_backup": "Security and backup", + "create_backup": "Create backup", + "privacy_settings": "Privacy settings", + "privacy": "Privacy", + "display_settings": "Display settings", + "other_settings": "Other settings", + "disable_exchange": "Disable exchange", "advanced_privacy_settings": "Advanced Privacy Settings", - "privacy_settings": "Privacy Settings", "settings_can_be_changed_later": "These settings can be changed later in the app settings" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 0edbfdfd0..738e64849 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "DESCONECTADO", "sync_status_connecting" : "CONECTANDO", "sync_status_connected" : "CONECTADO", + "sync_status_attempting_sync" : "INTENTAR SINCRONIZAR", "transaction_priority_slow" : "Lento", @@ -653,7 +654,14 @@ "use_suggested": "Usar sugerido", "do_not_share_warning_text" : "No comparta estos con nadie más, incluido el soporte.\n\n¡Te robarán tu dinero!", "help": "ayuda", + "connection_sync": "Conexión y sincronización", + "security_and_backup": "Seguridad y respaldo", + "create_backup": "Crear copia de seguridad", + "privacy_settings": "Configuración de privacidad", + "privacy": "Privacidad", + "display_settings": "Configuración de pantalla", + "other_settings": "Otras configuraciones", + "disable_exchange": "Deshabilitar intercambio", "advanced_privacy_settings": "Configuración avanzada de privacidad", - "privacy_settings": "La configuración de privacidad", "settings_can_be_changed_later": "Estas configuraciones se pueden cambiar más tarde en la configuración de la aplicación" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index d7047781f..f04a6181c 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -354,6 +354,7 @@ "sync_status_failed_connect" : "DÉCONNECTÉ", "sync_status_connecting" : "CONNEXION EN COURS", "sync_status_connected" : "CONNECTÉ", + "sync_status_attempting_sync" : "TENTATIVE DE SYNCHRONISATION", "transaction_priority_slow" : "Lent", @@ -651,7 +652,14 @@ "use_suggested": "Utilisation suggérée", "do_not_share_warning_text" : "Ne les partagez avec personne d'autre, y compris avec l'assistance.\n\nIls vont voler votre argent!", "help": "aider", - "advanced_privacy_settings": "Paramètres de confidentialité avancés", + "connection_sync": "Connexion et synchronisation", + "security_and_backup": "Sécurité et sauvegarde", + "create_backup": "Créer une sauvegarde", "privacy_settings": "Paramètres de confidentialité", + "privacy": "Confidentialité", + "display_settings": "Paramètres d'affichage", + "other_settings": "Autres paramètres", + "disable_exchange": "Désactiver l'échange", + "advanced_privacy_settings": "Paramètres de confidentialité avancés", "settings_can_be_changed_later": "Ces paramètres peuvent être modifiés ultérieurement dans les paramètres de l'application" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index ed83667dd..fdf565a7b 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "डिस्कनेक्ट किया गया", "sync_status_connecting" : "कनेक्ट", "sync_status_connected" : "जुड़े हुए", + "sync_status_attempting_sync" : "सिंक करने का प्रयास", "transaction_priority_slow" : "धीरे", @@ -653,7 +654,14 @@ "use_suggested": "सुझाए गए का प्रयोग करें", "do_not_share_warning_text" : "इन्हें समर्थन सहित किसी और के साथ साझा न करें।\n\nवे आपका पैसा चुरा लेंगे!", "help": "मदद करना", + "connection_sync": "कनेक्शन और सिंक", + "security_and_backup": "सुरक्षा और बैकअप", + "create_backup": "बैकअप बनाएँ", + "privacy_settings": "गोपनीयता सेटिंग्स", + "privacy": "गोपनीयता", + "display_settings": "प्रदर्शन सेटिंग्स", + "other_settings": "अन्य सेटिंग्स", + "disable_exchange": "एक्सचेंज अक्षम करें", "advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स", - "privacy_settings": "गोपनीय सेटिंग", "settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index a02cec6cc..ece52e656 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "ISKLJUČENO", "sync_status_connecting" : "SPAJANJE", "sync_status_connected" : "SPOJENO", + "sync_status_attempting_sync" : "POKUŠAJ SINKRONIZACIJE", "transaction_priority_slow" : "Sporo", @@ -653,7 +654,14 @@ "use_suggested": "Koristite predloženo", "do_not_share_warning_text" : "Nemojte ih dijeliti ni s kim, uključujući podršku.\n\nUkrast će vam novac!", "help": "pomozite", - "advanced_privacy_settings": "Napredne postavke privatnosti", + "connection_sync": "Povezivanje i sinkronizacija", + "security_and_backup": "Sigurnost i sigurnosna kopija", + "create_backup": "Stvori sigurnosnu kopiju", "privacy_settings": "Postavke privatnosti", + "privacy": "Privatnost", + "display_settings": "Postavke zaslona", + "other_settings": "Ostale postavke", + "disable_exchange": "Onemogući exchange", + "advanced_privacy_settings": "Napredne postavke privatnosti", "settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index ca82b5c86..d5f591c64 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "DISCONNESSO", "sync_status_connecting" : "CONNESSIONE", "sync_status_connected" : "CONNESSO", + "sync_status_attempting_sync" : "TENTATIVO DI SINCRONIZZAZIONE", "transaction_priority_slow" : "Bassa", @@ -653,7 +654,14 @@ "use_suggested": "Usa suggerito", "do_not_share_warning_text" : "Non condividerli con nessun altro, incluso il supporto.\n\nTi ruberanno i soldi!", "help": "aiuto", + "connection_sync": "Connessione e sincronizzazione", + "security_and_backup": "Sicurezza e backup", + "create_backup": "Crea backup", + "privacy_settings": "Impostazioni privacy", + "privacy": "Privacy", + "display_settings": "Impostazioni di visualizzazione", + "other_settings": "Altre impostazioni", + "disable_exchange": "Disabilita scambio", "advanced_privacy_settings": "Impostazioni avanzate sulla privacy", - "privacy_settings": "Impostazioni della privacy", "settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d48e1c69a..21c21c7a8 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "切断されました", "sync_status_connecting" : "接続中", "sync_status_connected" : "接続済み", + "sync_status_attempting_sync" : "同期を試みています", "transaction_priority_slow" : "スロー", @@ -653,7 +654,14 @@ "use_suggested": "推奨を使用", "do_not_share_warning_text" : "サポートを含め、これらを他の誰とも共有しないでください。\n\n彼らはあなたのお金を盗みます!", "help": "ヘルプ", - "advanced_privacy_settings": "高度なプライバシー設定", + "connection_sync": "接続と同期", + "security_and_backup": "セキュリティとバックアップ", + "create_backup": "バックアップを作成", "privacy_settings": "プライバシー設定", + "privacy": "プライバシー", + "display_settings": "表示設定", + "other_settings": "その他の設定", + "disable_exchange": "交換を無効にする", + "advanced_privacy_settings": "高度なプライバシー設定", "settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 2b818c3c8..0d2bc31bc 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "연결 해제", "sync_status_connecting" : "연결 중", "sync_status_connected" : "연결됨", + "sync_status_attempting_sync" : "동기화 시도 중", "transaction_priority_slow" : "느린", @@ -653,7 +654,14 @@ "use_suggested": "추천 사용", "do_not_share_warning_text" : "지원을 포함하여 다른 사람과 이러한 정보를 공유하지 마십시오.\n\n그들은 당신의 돈을 훔칠 것입니다!", "help": "돕다", + "connection_sync": "연결 및 동기화", + "security_and_backup": "보안 및 백업", + "create_backup": "백업 생성", + "privacy_settings": "개인정보 설정", + "privacy": "프라이버시", + "display_settings": "디스플레이 설정", + "other_settings": "기타 설정", + "disable_exchange": "교환 비활성화", "advanced_privacy_settings": "고급 개인 정보 설정", - "privacy_settings": "개인 정보 설정", "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다." } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 0c470c2d9..b7bb86d9c 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "LOSGEKOPPELD", "sync_status_connecting" : "AANSLUITING", "sync_status_connected" : "VERBONDEN", + "sync_status_attempting_sync" : "SYNCHRONISATIE PROBEREN", "transaction_priority_slow" : "Langzaam", @@ -653,7 +654,14 @@ "use_suggested": "Gebruik aanbevolen", "do_not_share_warning_text" : "Deel deze met niemand anders, ook niet met support.\n\nZe zullen je geld stelen!", "help": "helpen", + "connection_sync": "Verbinding en synchronisatie", + "security_and_backup": "Beveiliging en back-up", + "create_backup": "Maak een back-up", + "privacy_settings": "Privacy-instellingen", + "privacy": "Privacy", + "display_settings": "Weergave-instellingen", + "other_settings": "Andere instellingen", + "disable_exchange": "Uitwisseling uitschakelen", "advanced_privacy_settings": "Geavanceerde privacy-instellingen", - "privacy_settings": "Privacy instellingen", "settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index aaa141cbc..20a0b2f98 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "NIEPOWIĄZANY", "sync_status_connecting" : "ZŁĄCZONY", "sync_status_connected" : "POŁĄCZONY", + "sync_status_attempting_sync" : "PRÓBA SYNCHRONIZACJI", "transaction_priority_slow" : "Powolny", @@ -653,7 +654,14 @@ "use_suggested": "Użyj sugerowane", "do_not_share_warning_text" : "Nie udostępniaj ich nikomu innemu, w tym wsparcia.\n\nUkradną twoje pieniądze!", "help": "pomoc", - "advanced_privacy_settings": "Zaawansowane ustawienia prywatności", + "connection_sync": "Połączenie i synchronizacja", + "security_and_backup": "Bezpieczeństwo i kopia zapasowa", + "create_backup": "Utwórz kopię zapasową", "privacy_settings": "Ustawienia prywatności", + "privacy": "Prywatność", + "display_settings": "Ustawienia wyświetlania", + "other_settings": "Inne ustawienia", + "disable_exchange": "Wyłącz wymianę", + "advanced_privacy_settings": "Zaawansowane ustawienia prywatności", "settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 58dfebef8..65438a26c 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "DESCONECTADO", "sync_status_connecting" : "CONECTANDO", "sync_status_connected" : "CONECTADO", + "sync_status_attempting_sync" : "TENTANDO SINCRONIZAR", "transaction_priority_slow" : "Lenta", @@ -652,7 +653,14 @@ "use_suggested": "Uso sugerido", "do_not_share_warning_text" : "Não os compartilhe com mais ninguém, incluindo suporte.\n\nEles vão roubar seu dinheiro!", "help": "ajuda", - "advanced_privacy_settings": "Configurações de privacidade avançadas", + "connection_sync": "Conexão e sincronização", + "security_and_backup": "Segurança e backup", + "create_backup": "Criar backup", "privacy_settings": "Configurações de privacidade", + "privacy": "Privacidade", + "display_settings": "Configurações de exibição", + "other_settings": "Outras configurações", + "disable_exchange": "Desativar troca", + "advanced_privacy_settings": "Configurações de privacidade avançadas", "settings_can_be_changed_later": "Essas configurações podem ser alteradas posteriormente nas configurações do aplicativo" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 241f91aee..4dcda3007 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "ОТКЛЮЧЕНО", "sync_status_connecting" : "ПОДКЛЮЧЕНИЕ", "sync_status_connected" : "ПОДКЛЮЧЕНО", + "sync_status_attempting_sync" : "ПОПЫТКА СИНХРОНИЗАЦИИ", "transaction_priority_slow" : "Медленный", @@ -653,7 +654,14 @@ "use_suggested": "Использовать предложенный", "do_not_share_warning_text" : "Не делитесь ими с кем-либо еще, в том числе со службой поддержки.\n\nОни украдут ваши деньги!", "help": "помощь", - "advanced_privacy_settings": "Расширенные настройки конфиденциальности", + "connection_sync": "Подключение и синхронизация", + "security_and_backup": "Безопасность и резервное копирование", + "create_backup": "Создать резервную копию", "privacy_settings": "Настройки конфиденциальности", + "privacy": "Конфиденциальность", + "display_settings": "Настройки отображения", + "other_settings": "Другие настройки", + "disable_exchange": "Отключить обмен", + "advanced_privacy_settings": "Расширенные настройки конфиденциальности", "settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 94f4449c8..61f6cfb0d 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -355,6 +355,7 @@ "sync_status_failed_connect" : "ВІДКЛЮЧЕНО", "sync_status_connecting" : "ПІДКЛЮЧЕННЯ", "sync_status_connected" : "ПІДКЛЮЧЕНО", + "sync_status_attempting_sync" : "СПРОБА СИНХРОНІЗАЦІЇ", "transaction_priority_slow" : "Повільний", @@ -652,7 +653,14 @@ "use_suggested": "Використати запропоноване", "do_not_share_warning_text" : "Не повідомляйте їх нікому, включно зі службою підтримки.\n\nВони вкрадуть ваші гроші!", "help": "допомога", + "connection_sync": "Підключення та синхронізація", + "security_and_backup": "Безпека та резервне копіювання", + "create_backup": "Створити резервну копію", + "privacy_settings": "Налаштування конфіденційності", + "privacy": "Конфіденційність", + "display_settings": "Налаштування дисплея", + "other_settings": "Інші налаштування", + "disable_exchange": "Вимкнути exchange", "advanced_privacy_settings": "Розширені налаштування конфіденційності", - "privacy_settings": "Параметри конфіденційності", "settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 650ec723a..750b4e605 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -356,6 +356,7 @@ "sync_status_failed_connect" : "断线", "sync_status_connecting" : "连接中", "sync_status_connected" : "已连接", + "sync_status_attempting_sync" : "嘗試同步", "transaction_priority_slow" : "慢速", @@ -651,7 +652,14 @@ "use_suggested": "使用建议", "do_not_share_warning_text" : "不要與其他任何人分享這些內容,包括支持。\n\n他們會偷你的錢!", "help": "帮助", - "advanced_privacy_settings": "高级隐私设置", + "connection_sync": "连接和同步", + "security_and_backup": "安全和备份", + "create_backup": "创建备份", "privacy_settings": "隐私设置", + "privacy":"隐私", + "display_settings": "显示设置", + "other_settings": "其他设置", + "disable_exchange": "禁用交换", + "advanced_privacy_settings": "高级隐私设置", "settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置" } diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index b9ca9f98b..d361c8dfa 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.2.0" -MONERO_COM_BUILD_NUMBER=26 +MONERO_COM_VERSION="1.2.1" +MONERO_COM_BUILD_NUMBER=32 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.5.0" -CAKEWALLET_BUILD_NUMBER=130 +CAKEWALLET_VERSION="4.5.1" +CAKEWALLET_BUILD_NUMBER=136 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index bab932345..8bb945680 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.2.0" -MONERO_COM_BUILD_NUMBER=26 +MONERO_COM_VERSION="1.2.1" +MONERO_COM_BUILD_NUMBER=29 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.5.0" -CAKEWALLET_BUILD_NUMBER=130 +CAKEWALLET_VERSION="4.5.1" +CAKEWALLET_BUILD_NUMBER=133 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 64c7beefd..e5202a829 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -28,6 +28,7 @@ class SecretKey { SecretKey('simpleSwapApiKey', () => ''), SecretKey('anypayToken', () => ''), SecretKey('onramperApiKey', () => ''), + SecretKey('ioniaClientId', () => ''), ]; final String name;