mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-26 00:58:50 +00:00
Merge remote-tracking branch 'origin/paynyms' into majestic_bank
This commit is contained in:
commit
773c5fad9c
79 changed files with 1469 additions and 553 deletions
assets
crypto_plugins
lib
db
exceptions/main_db
models/isar/models/address
pages
address_book_views/subviews
buy_view
exchange_view
home_view
paynym
receive_view
send_view
settings_views
global_settings_view
wallet_settings_view/wallet_network_settings_view
wallet_view
wallets_view/sub_widgets
pages_desktop_specific
address_book_view/subwidgets
desktop_exchange
exchange_steps/subwidgets
subwidgets
my_stack_view
desktop_favorite_wallets.dartmy_wallets.dart
wallet_view/sub_widgets
password
settings/settings_menu
providers/blockchain/dogecoin
route_generator.dartservices
buy
coins
bitcoin
bitcoincash
dogecoin
epiccash
firo
litecoin
namecoin
particl
mixins
utilities
widgets
macos/Flutter
pubspec.lockpubspec.yamltest/widget_tests
windows/flutter
BIN
assets/images/unclaimed.png
Normal file
BIN
assets/images/unclaimed.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 756 KiB |
|
@ -1,11 +1,18 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_908_22491)">
|
||||
<g opacity="0.4">
|
||||
<path d="M22.2 6C23.3297 5.37187 24 4.59422 24 3.75C24 1.67906 19.9688 0 15 0C9.98906 0 6 1.67906 6 3.75C6 4.59422 6.67031 5.37187 7.8 6C7.80937 6.00469 7.81758 6.00937 7.82578 6.01406C7.83398 6.01875 7.84219 6.02344 7.85156 6.02813C8.23125 6.00938 8.61094 6 9 6C11.6344 6 14.0906 6.44062 15.9422 7.21406C16.1203 7.28906 16.2984 7.36875 16.4672 7.44844C18.8062 7.28906 20.8359 6.75469 22.2 6Z" fill="#232323"/>
|
||||
<path d="M19.9435 12.9151C19.7958 12.9551 19.6477 12.9951 19.5 13.0359V13.5C20.7602 13.5 21.9296 13.8885 22.8951 14.5522C23.5995 14.0172 24 13.4028 24 12.75V11.0906C23.4141 11.5734 22.7063 11.9672 21.9422 12.2859C21.3382 12.5376 20.6447 12.7253 19.9435 12.9151Z" fill="#232323"/>
|
||||
<path d="M18.3703 8.74688C19.0031 9.37969 19.5 10.2234 19.5 11.25V11.4984C20.4328 11.2734 21.2625 10.9781 21.9469 10.6359C21.9739 10.6209 22.0009 10.6021 22.0279 10.5833C22.0852 10.5432 22.1426 10.5032 22.2 10.5C23.3297 9.87187 24 9.09375 24 8.25V6.59063C23.4141 7.07344 22.7063 7.46719 21.9422 7.78594C20.9109 8.2125 19.6969 8.54063 18.3703 8.74688Z" fill="#232323"/>
|
||||
<path d="M22.2 6C23.3297 5.37187 24 4.59422 24 3.75C24 1.67906 19.9688 0 15 0C9.98906 0 6 1.67906 6 3.75C6 4.59422 6.67031 5.37187 7.8 6C7.80937 6.00469 7.81758 6.00937 7.82578 6.01406C7.83398 6.01875 7.84219 6.02344 7.85156 6.02813C8.23125 6.00938 8.61094 6 9 6C11.6344 6 14.0906 6.44062 15.9422 7.21406C16.1203 7.28906 16.2984 7.36875 16.4672 7.44844C18.8062 7.28906 20.8359 6.75469 22.2 6Z" fill="#D12B41"/>
|
||||
<path d="M19.9435 12.9151C19.7958 12.9551 19.6477 12.9951 19.5 13.0359V13.5C20.7602 13.5 21.9296 13.8885 22.8951 14.5522C23.5995 14.0172 24 13.4028 24 12.75V11.0906C23.4141 11.5734 22.7063 11.9672 21.9422 12.2859C21.3382 12.5376 20.6447 12.7253 19.9435 12.9151Z" fill="#D12B41"/>
|
||||
<path d="M18.3703 8.74688C19.0031 9.37969 19.5 10.2234 19.5 11.25V11.4984C20.4328 11.2734 21.2625 10.9781 21.9469 10.6359C21.9739 10.6209 22.0009 10.6021 22.0279 10.5833C22.0852 10.5432 22.1426 10.5032 22.2 10.5C23.3297 9.87187 24 9.09375 24 8.25V6.59063C23.4141 7.07344 22.7063 7.46719 21.9422 7.78594C20.9109 8.2125 19.6969 8.54063 18.3703 8.74688Z" fill="#D12B41"/>
|
||||
</g>
|
||||
<path d="M16.2 13.5C17.3297 12.8719 18 12.0938 18 11.25C18 9.17813 13.9688 7.5 9 7.5C4.02938 7.5 0 9.17813 0 11.25C0 12.0938 0.669375 12.8719 1.79953 13.5C1.85443 13.5031 1.91057 13.5415 1.96782 13.5807C1.9966 13.6004 2.02567 13.6203 2.055 13.6359C3.70594 14.4703 6.20625 15 9 15C11.9438 15 14.5594 14.4094 16.2 13.5Z" fill="#232323"/>
|
||||
<path d="M14.8788 15.6729C13.1948 16.2046 11.1571 16.5 9 16.5C6.36562 16.5 3.91125 16.0594 2.05922 15.2859C1.29469 14.9672 0.583594 14.5734 0 14.0906V15.75C0 16.5938 0.669375 17.3719 1.79953 18C3.44109 18.9094 6.05625 19.5 9 19.5C10.6471 19.5 12.1916 19.3159 13.5211 18.9937C13.6261 17.7367 14.1186 16.5898 14.8788 15.6729Z" fill="#232323"/>
|
||||
<path d="M13.5862 20.5191C13.7529 21.4936 14.1547 22.3879 14.731 23.1415C13.1742 23.6778 11.1771 24 9 24C4.02938 24 0 22.3219 0 20.25V18.5906C0.583594 19.0734 1.29469 19.4672 2.05922 19.7859C3.91125 20.5594 6.36562 21 9 21C10.6307 21 12.1932 20.8312 13.5862 20.5191Z" fill="#232323"/>
|
||||
<path d="M24 19.5C24 21.9844 21.9844 24 19.5 24C17.0156 24 15 21.9844 15 19.5C15 17.0156 17.0156 15 19.5 15C21.9844 15 24 17.0156 24 19.5ZM19 17.4719V18.9719H17.5C17.225 18.9719 17 19.225 17 19.4719C17 19.775 17.225 19.9719 17.5 19.9719H19V21.4719C19 21.775 19.225 21.9719 19.5 21.9719C19.775 21.9719 20 21.775 20 21.4719V19.9719H21.5C21.775 19.9719 22 19.775 22 19.4719C22 19.225 21.775 18.9719 21.5 18.9719H20V17.4719C20 17.225 19.775 16.9719 19.5 16.9719C19.225 16.9719 19 17.225 19 17.4719Z" fill="#232323"/>
|
||||
<path d="M16.2 13.5C17.3297 12.8719 18 12.0938 18 11.25C18 9.17813 13.9688 7.5 9 7.5C4.02938 7.5 0 9.17813 0 11.25C0 12.0938 0.669375 12.8719 1.79953 13.5C1.85443 13.5031 1.91057 13.5415 1.96782 13.5807C1.9966 13.6004 2.02567 13.6203 2.055 13.6359C3.70594 14.4703 6.20625 15 9 15C11.9438 15 14.5594 14.4094 16.2 13.5Z" fill="#D12B41"/>
|
||||
<path d="M14.8788 15.6729C13.1948 16.2046 11.1571 16.5 9 16.5C6.36562 16.5 3.91125 16.0594 2.05922 15.2859C1.29469 14.9672 0.583594 14.5734 0 14.0906V15.75C0 16.5938 0.669375 17.3719 1.79953 18C3.44109 18.9094 6.05625 19.5 9 19.5C10.6471 19.5 12.1916 19.3159 13.5211 18.9937C13.6261 17.7367 14.1186 16.5898 14.8788 15.6729Z" fill="#D12B41"/>
|
||||
<path d="M13.5862 20.5191C13.7529 21.4936 14.1547 22.3879 14.731 23.1415C13.1742 23.6778 11.1771 24 9 24C4.02938 24 0 22.3219 0 20.25V18.5906C0.583594 19.0734 1.29469 19.4672 2.05922 19.7859C3.91125 20.5594 6.36562 21 9 21C10.6307 21 12.1932 20.8312 13.5862 20.5191Z" fill="#D12B41"/>
|
||||
<path d="M24 19.5C24 21.9844 21.9844 24 19.5 24C17.0156 24 15 21.9844 15 19.5C15 17.0156 17.0156 15 19.5 15C21.9844 15 24 17.0156 24 19.5ZM19 17.4719V18.9719H17.5C17.225 18.9719 17 19.225 17 19.4719C17 19.775 17.225 19.9719 17.5 19.9719H19V21.4719C19 21.775 19.225 21.9719 19.5 21.9719C19.775 21.9719 20 21.775 20 21.4719V19.9719H21.5C21.775 19.9719 22 19.775 22 19.4719C22 19.225 21.775 18.9719 21.5 18.9719H20V17.4719C20 17.225 19.775 16.9719 19.5 16.9719C19.225 16.9719 19 17.225 19 17.4719Z" fill="#D12B41"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_908_22491">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
Before (image error) Size: 2.6 KiB After (image error) Size: 2.7 KiB |
|
@ -1,4 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6695 1.06575C15.1855 0.607104 15.9756 0.65358 16.4343 1.16956L20.4343 5.66956C20.8552 6.14317 20.8552 6.85686 20.4343 7.33047L16.4343 11.8305C15.9756 12.3464 15.1855 12.3929 14.6695 11.9343C14.1536 11.4756 14.1071 10.6855 14.5657 10.1696L16.7164 7.75001H6C4.48122 7.75001 3.25 8.98123 3.25 10.5C3.25 11.1904 2.69036 11.75 2 11.75C1.30964 11.75 0.75 11.1904 0.75 10.5C0.75 7.60052 3.10051 5.25001 6 5.25001H16.7164L14.5657 2.83047C14.1071 2.31449 14.1536 1.5244 14.6695 1.06575Z" fill="#232323"/>
|
||||
<path opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M9.33045 23.4342C8.81448 23.8929 8.02439 23.8464 7.56574 23.3304L3.56574 18.8304C3.14475 18.3568 3.14475 17.6431 3.56574 17.1695L7.56574 12.6695C8.02439 12.1536 8.81448 12.1071 9.33046 12.5657C9.84643 13.0244 9.89291 13.8145 9.43426 14.3304L7.28355 16.75L18 16.75C19.5188 16.75 20.75 15.5188 20.75 14C20.75 13.3096 21.3096 12.75 22 12.75C22.6904 12.75 23.25 13.3096 23.25 14C23.25 16.8995 20.8995 19.25 18 19.25L7.28355 19.25L9.43426 21.6695C9.89291 22.1855 9.84643 22.9756 9.33045 23.4342Z" fill="#232323"/>
|
||||
<path d="M19.5 6.5H6C3.79086 6.5 2 8.29086 2 10.5M19.5 6.5L15.5 2M19.5 6.5L15.5 11" stroke="#D12B41" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path opacity="0.4" d="M4.5 18L18 18C20.2091 18 22 16.2091 22 14M4.5 18L8.5 22.5M4.5 18L8.5 13.5" stroke="#D12B41" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
|
Before (image error) Size: 1.2 KiB After (image error) Size: 455 B |
3
assets/svg/robot-head.svg
Normal file
3
assets/svg/robot-head.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.4173 5.41699V5.91699H10.9173H14.1257C15.1181 5.91699 15.9173 6.71825 15.9173 7.70866V15.5003C15.9173 16.2377 15.3213 16.8337 14.584 16.8337H5.41732C4.67997 16.8337 4.08398 16.2377 4.08398 15.5003V7.70866C4.08398 6.71866 4.88565 5.91699 5.87565 5.91699H9.08398H9.58398V5.41699V3.58366C9.58398 3.35506 9.77205 3.16699 10.0007 3.16699C10.2292 3.16699 10.4173 3.35506 10.4173 3.58366V5.41699ZM8.16732 15.0837H8.58398H8.66732H9.08398H10.9173H11.334H11.4173H11.834H13.6673H14.1673V14.5837V13.667V13.167H13.6673H11.834H11.4173H11.334H10.9173H9.08398H8.66732H8.58398H8.16732H6.33398H5.83398V13.667V14.5837V15.0837H6.33398H8.16732ZM1.33398 10.0003C1.33398 9.89019 1.37745 9.7852 1.45595 9.70663C1.53524 9.62741 1.64114 9.58366 1.75065 9.58366H2.16732V14.0837H1.75065C1.64023 14.0837 1.53452 14.0398 1.45609 13.9614C1.37774 13.8831 1.33398 13.7775 1.33398 13.667V10.0003ZM5.60482 10.0003C5.60482 10.9095 6.34144 11.6462 7.25065 11.6462C8.15987 11.6462 8.89648 10.9095 8.89648 10.0003C8.89648 9.09111 8.15987 8.35449 7.25065 8.35449C6.34144 8.35449 5.60482 9.09111 5.60482 10.0003ZM11.1048 10.0003C11.1048 10.9101 11.8409 11.6462 12.7507 11.6462C13.6599 11.6462 14.3965 10.9095 14.3965 10.0003C14.3965 9.09111 13.6599 8.35449 12.7507 8.35449C11.8416 8.35449 11.1048 9.09037 11.1048 10.0003ZM18.2507 9.58366C18.3599 9.58366 18.4652 9.62719 18.5445 9.70648C18.6238 9.78578 18.6673 9.89112 18.6673 10.0003V13.667C18.6673 13.7774 18.6235 13.8831 18.5451 13.9616C18.4667 14.0399 18.3612 14.0837 18.2507 14.0837H17.834V9.58366H18.2507Z" fill="#8E9192" stroke="#8E9192"/>
|
||||
</svg>
|
After (image error) Size: 1.6 KiB |
3
assets/svg/whirlpool.svg
Normal file
3
assets/svg/whirlpool.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.16811 18.29C4.45149 18.089 3.04304 17.5064 1.84171 16.5006C1.52855 16.2383 1.08929 15.7904 0.868214 15.5078C0.795124 15.4144 0.815885 15.4205 1.16357 15.5943C2.17257 16.0986 3.56529 16.3941 4.67986 16.3404C5.4666 16.3025 6.00361 16.1827 6.16709 16.0087C6.30087 15.8663 6.24663 15.772 5.85013 15.458C4.68271 14.5333 3.91789 13.4633 3.48109 12.1436C2.95876 10.5655 3.03006 8.85291 3.68121 7.33745C3.85982 6.92178 4.22353 6.25056 4.46108 5.89825C5.0569 5.01461 6.02359 4.09702 7.04772 3.443C7.53459 3.13207 8.67134 2.57608 9.28978 2.34638C10.1175 2.03895 11.0493 1.81907 11.9322 1.72278C12.5152 1.6592 13.8654 1.6973 14.3766 1.79177C15.7252 2.04096 16.8058 2.50254 17.8199 3.26249C18.1679 3.52329 18.8273 4.15363 19.0478 4.43636L19.1673 4.58953L18.7556 4.39158C17.6907 3.87949 16.4125 3.60951 15.2969 3.66106C13.9736 3.72218 13.4271 3.9979 13.8908 4.37051C14.4654 4.83219 14.718 5.05501 14.9678 5.32051C16.047 6.46745 16.6696 7.8829 16.8049 9.49648C16.8912 10.527 16.6998 11.6803 16.2805 12.6564C15.6851 14.0423 14.7972 15.1697 13.573 16.0943C13.0338 16.5014 12.6196 16.756 11.9526 17.09C10.795 17.6696 9.68552 18.033 8.49122 18.2239C8.12694 18.2821 6.49683 18.3285 6.16811 18.29ZM10.567 12.7788C11.6493 12.5494 12.5288 11.6694 12.7584 10.586C12.824 10.2766 12.8238 9.72298 12.7584 9.41267C12.6408 8.85789 12.4049 8.43337 11.9758 8.00424C11.3957 7.42417 10.7838 7.17097 9.95841 7.16954C9.16291 7.16743 8.43295 7.4925 7.88801 8.0894C7.39072 8.63413 7.14979 9.25773 7.14979 10.0002C7.14979 10.8011 7.40895 11.4213 7.98385 11.9962C8.40667 12.419 8.83591 12.6598 9.37601 12.7773C9.67249 12.8418 10.2667 12.8425 10.5673 12.7794L10.567 12.7788Z" fill="#8E9192"/>
|
||||
</svg>
|
After (image error) Size: 1.7 KiB |
|
@ -1 +1 @@
|
|||
Subproject commit 0309140a95a51388df0effcc39ff0a25b2752b29
|
||||
Subproject commit 9a150d8cd2c3625424b0059e6b7306f3659fdbe0
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -40,64 +41,101 @@ class MainDB {
|
|||
String walletId) =>
|
||||
isar.addresses.where().walletIdEqualTo(walletId);
|
||||
|
||||
Future<void> putAddress(Address address) => isar.writeTxn(() async {
|
||||
await isar.addresses.put(address);
|
||||
Future<int> putAddress(Address address) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.addresses.put(address);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putAddress: $address", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> putAddresses(List<Address> addresses) => isar.writeTxn(() async {
|
||||
await isar.addresses.putAll(addresses);
|
||||
Future<List<int>> putAddresses(List<Address> addresses) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.addresses.putAll(addresses);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putAddresses: $addresses", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateOrPutAddresses(List<Address> addresses) async {
|
||||
await isar.writeTxn(() async {
|
||||
for (final address in addresses) {
|
||||
final storedAddress = await isar.addresses
|
||||
.getByValueWalletId(address.value, address.walletId);
|
||||
Future<List<int>> updateOrPutAddresses(List<Address> addresses) async {
|
||||
try {
|
||||
List<int> ids = [];
|
||||
await isar.writeTxn(() async {
|
||||
for (final address in addresses) {
|
||||
final storedAddress = await isar.addresses
|
||||
.getByValueWalletId(address.value, address.walletId);
|
||||
|
||||
if (storedAddress == null) {
|
||||
await isar.addresses.put(address);
|
||||
} else {
|
||||
address.id = storedAddress.id;
|
||||
await storedAddress.transactions.load();
|
||||
final txns = storedAddress.transactions.toList();
|
||||
await isar.addresses.delete(storedAddress.id);
|
||||
await isar.addresses.put(address);
|
||||
address.transactions.addAll(txns);
|
||||
await address.transactions.save();
|
||||
int id;
|
||||
if (storedAddress == null) {
|
||||
id = await isar.addresses.put(address);
|
||||
} else {
|
||||
address.id = storedAddress.id;
|
||||
await storedAddress.transactions.load();
|
||||
final txns = storedAddress.transactions.toList();
|
||||
await isar.addresses.delete(storedAddress.id);
|
||||
id = await isar.addresses.put(address);
|
||||
address.transactions.addAll(txns);
|
||||
await address.transactions.save();
|
||||
}
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return ids;
|
||||
} catch (e) {
|
||||
throw MainDBException("failed updateOrPutAddresses: $addresses", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Address?> getAddress(String walletId, String address) async {
|
||||
return isar.addresses.getByValueWalletId(address, walletId);
|
||||
}
|
||||
|
||||
Future<void> updateAddress(Address oldAddress, Address newAddress) =>
|
||||
isar.writeTxn(() async {
|
||||
Future<int> updateAddress(Address oldAddress, Address newAddress) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
newAddress.id = oldAddress.id;
|
||||
await oldAddress.transactions.load();
|
||||
final txns = oldAddress.transactions.toList();
|
||||
await isar.addresses.delete(oldAddress.id);
|
||||
await isar.addresses.put(newAddress);
|
||||
final id = await isar.addresses.put(newAddress);
|
||||
newAddress.transactions.addAll(txns);
|
||||
await newAddress.transactions.save();
|
||||
return id;
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException(
|
||||
"failed updateAddress: from=$oldAddress to=$newAddress", e);
|
||||
}
|
||||
}
|
||||
|
||||
// transactions
|
||||
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(
|
||||
String walletId) =>
|
||||
isar.transactions.where().walletIdEqualTo(walletId);
|
||||
|
||||
Future<void> putTransaction(Transaction transaction) =>
|
||||
isar.writeTxn(() async {
|
||||
await isar.transactions.put(transaction);
|
||||
Future<int> putTransaction(Transaction transaction) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.transactions.put(transaction);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putTransaction: $transaction", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> putTransactions(List<Transaction> transactions) =>
|
||||
isar.writeTxn(() async {
|
||||
await isar.transactions.putAll(transactions);
|
||||
Future<List<int>> putTransactions(List<Transaction> transactions) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.transactions.putAll(transactions);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putTransactions: $transactions", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Transaction?> getTransaction(String walletId, String txid) async {
|
||||
return isar.transactions.getByTxidWalletId(txid, walletId);
|
||||
|
@ -212,55 +250,60 @@ class MainDB {
|
|||
}
|
||||
|
||||
Future<void> addNewTransactionData(
|
||||
List<Tuple4<Transaction, List<Output>, List<Input>, Address?>>
|
||||
transactionsData,
|
||||
String walletId) async {
|
||||
await isar.writeTxn(() async {
|
||||
for (final data in transactionsData) {
|
||||
final tx = data.item1;
|
||||
List<Tuple4<Transaction, List<Output>, List<Input>, Address?>>
|
||||
transactionsData,
|
||||
String walletId,
|
||||
) async {
|
||||
try {
|
||||
await isar.writeTxn(() async {
|
||||
for (final data in transactionsData) {
|
||||
final tx = data.item1;
|
||||
|
||||
final potentiallyUnconfirmedTx = await getTransactions(walletId)
|
||||
.filter()
|
||||
.txidEqualTo(tx.txid)
|
||||
.findFirst();
|
||||
if (potentiallyUnconfirmedTx != null) {
|
||||
// update use id to replace tx
|
||||
tx.id = potentiallyUnconfirmedTx.id;
|
||||
await isar.transactions.delete(potentiallyUnconfirmedTx.id);
|
||||
}
|
||||
// save transaction
|
||||
await isar.transactions.put(tx);
|
||||
|
||||
// link and save outputs
|
||||
if (data.item2.isNotEmpty) {
|
||||
await isar.outputs.putAll(data.item2);
|
||||
tx.outputs.addAll(data.item2);
|
||||
await tx.outputs.save();
|
||||
}
|
||||
|
||||
// link and save inputs
|
||||
if (data.item3.isNotEmpty) {
|
||||
await isar.inputs.putAll(data.item3);
|
||||
tx.inputs.addAll(data.item3);
|
||||
await tx.inputs.save();
|
||||
}
|
||||
|
||||
if (data.item4 != null) {
|
||||
final address = await getAddresses(walletId)
|
||||
final potentiallyUnconfirmedTx = await getTransactions(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(data.item4!.value)
|
||||
.txidEqualTo(tx.txid)
|
||||
.findFirst();
|
||||
if (potentiallyUnconfirmedTx != null) {
|
||||
// update use id to replace tx
|
||||
tx.id = potentiallyUnconfirmedTx.id;
|
||||
await isar.transactions.delete(potentiallyUnconfirmedTx.id);
|
||||
}
|
||||
// save transaction
|
||||
await isar.transactions.put(tx);
|
||||
|
||||
// check if address exists in db and add if it does not
|
||||
if (address == null) {
|
||||
await isar.addresses.put(data.item4!);
|
||||
// link and save outputs
|
||||
if (data.item2.isNotEmpty) {
|
||||
await isar.outputs.putAll(data.item2);
|
||||
tx.outputs.addAll(data.item2);
|
||||
await tx.outputs.save();
|
||||
}
|
||||
|
||||
// link and save address
|
||||
tx.address.value = address ?? data.item4!;
|
||||
await tx.address.save();
|
||||
// link and save inputs
|
||||
if (data.item3.isNotEmpty) {
|
||||
await isar.inputs.putAll(data.item3);
|
||||
tx.inputs.addAll(data.item3);
|
||||
await tx.inputs.save();
|
||||
}
|
||||
|
||||
if (data.item4 != null) {
|
||||
final address = await getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(data.item4!.value)
|
||||
.findFirst();
|
||||
|
||||
// check if address exists in db and add if it does not
|
||||
if (address == null) {
|
||||
await isar.addresses.put(data.item4!);
|
||||
}
|
||||
|
||||
// link and save address
|
||||
tx.address.value = address ?? data.item4!;
|
||||
await tx.address.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed addNewTransactionData", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
lib/exceptions/main_db/main_db_exception.dart
Normal file
12
lib/exceptions/main_db/main_db_exception.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
|
||||
class MainDBException extends SWException {
|
||||
MainDBException(super.message, this.originalError);
|
||||
|
||||
final Object originalError;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "$message: originalError=$originalError";
|
||||
}
|
||||
}
|
|
@ -69,6 +69,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
"}";
|
||||
}
|
||||
|
||||
// do not modify
|
||||
enum AddressType {
|
||||
p2pkh,
|
||||
p2sh,
|
||||
|
@ -79,6 +80,7 @@ enum AddressType {
|
|||
nonWallet,
|
||||
}
|
||||
|
||||
// do not modify
|
||||
enum AddressSubType {
|
||||
receiving,
|
||||
change,
|
||||
|
|
|
@ -584,7 +584,7 @@ class _AddAddressBookEntryViewState
|
|||
"Address ${i + 1}",
|
||||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
onTap: () {
|
||||
_removeForm(forms[i].id);
|
||||
},
|
||||
|
@ -601,7 +601,7 @@ class _AddAddressBookEntryViewState
|
|||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
onTap: () {
|
||||
_addForm();
|
||||
scrollController.animateTo(
|
||||
|
|
|
@ -311,7 +311,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
|
|||
"Addresses",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -108,9 +108,36 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
|
||||
static String boundedCryptoTicker = '';
|
||||
|
||||
void fiatFieldOnChanged(String value) async {}
|
||||
String _amountOutOfRangeErrorString = "";
|
||||
void validateAmount() {
|
||||
if (_buyAmountController.text.isEmpty) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString = "";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
void cryptoFieldOnChanged(String value) async {}
|
||||
final value = Decimal.tryParse(_buyAmountController.text);
|
||||
if (value == null) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString = "Invalid amount";
|
||||
});
|
||||
} else if (value > maxFiat && buyWithFiat) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString =
|
||||
"Maximum amount: ${maxFiat.toStringAsFixed(2)}";
|
||||
});
|
||||
} else if (value < minFiat && buyWithFiat) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString =
|
||||
"Minimum amount: ${minFiat.toStringAsFixed(2)}";
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void selectCrypto() async {
|
||||
if (ref.read(simplexProvider).supportedCryptos.isEmpty) {
|
||||
|
@ -253,6 +280,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
minFiat = fiat.minAmount != minFiat ? fiat.minAmount : minFiat;
|
||||
maxFiat = fiat.maxAmount != maxFiat ? fiat.maxAmount : maxFiat;
|
||||
});
|
||||
validateAmount();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -359,11 +387,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
}
|
||||
|
||||
String? _fetchIconUrlFromTicker(String? ticker) {
|
||||
if (ticker == null) return null;
|
||||
|
||||
return null;
|
||||
}
|
||||
// String? _fetchIconUrlFromTicker(String? ticker) {
|
||||
// if (ticker == null) return null;
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
|
||||
bool isStackCoin(String? ticker) {
|
||||
if (ticker == null) return false;
|
||||
|
@ -377,13 +405,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
|
||||
Widget? getIconForTicker(String ticker) {
|
||||
String? iconAsset = /*isStackCoin(ticker)
|
||||
String iconAsset = /*isStackCoin(ticker)
|
||||
?*/
|
||||
Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker));
|
||||
// : Assets.svg.buyIconFor(ticker);
|
||||
return (iconAsset != null)
|
||||
? SvgPicture.asset(iconAsset, height: 20, width: 20)
|
||||
: null;
|
||||
// return (iconAsset != null)
|
||||
// ? SvgPicture.asset(iconAsset, height: 20, width: 20)
|
||||
// : null;
|
||||
|
||||
return SvgPicture.asset(iconAsset, height: 20, width: 20);
|
||||
}
|
||||
|
||||
Future<void> previewQuote(SimplexQuote quote) async {
|
||||
|
@ -501,25 +531,25 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
} else {
|
||||
// Error; probably amount out of bounds
|
||||
String errorMessage = "${quoteResponse.exception?.errorMessage}";
|
||||
if (errorMessage.contains('must be between')) {
|
||||
errorMessage = errorMessage.substring(
|
||||
(errorMessage.indexOf('getQuote exception: ') ?? 19) + 20,
|
||||
errorMessage.indexOf(", value: null"));
|
||||
_BuyFormState.boundedCryptoTicker = errorMessage.substring(
|
||||
errorMessage.indexOf('The ') + 4,
|
||||
errorMessage.indexOf(' amount must be between'));
|
||||
_BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
|
||||
errorMessage.indexOf('must be between ') + 16,
|
||||
errorMessage.indexOf(' and ')));
|
||||
_BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
|
||||
errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
|
||||
errorMessage.length));
|
||||
if (Decimal.parse(_buyAmountController.text) >
|
||||
_BuyFormState.maxCrypto) {
|
||||
_buyAmountController.text = _BuyFormState.maxCrypto.toString();
|
||||
}
|
||||
}
|
||||
// String errorMessage = "${quoteResponse.exception?.errorMessage}";
|
||||
// if (errorMessage.contains('must be between')) {
|
||||
// errorMessage = errorMessage.substring(
|
||||
// errorMessage.indexOf('getQuote exception: ') + 20,
|
||||
// errorMessage.indexOf(", value: null"));
|
||||
// _BuyFormState.boundedCryptoTicker = errorMessage.substring(
|
||||
// errorMessage.indexOf('The ') + 4,
|
||||
// errorMessage.indexOf(' amount must be between'));
|
||||
// _BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
|
||||
// errorMessage.indexOf('must be between ') + 16,
|
||||
// errorMessage.indexOf(' and ')));
|
||||
// _BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
|
||||
// errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
|
||||
// errorMessage.length));
|
||||
// if (Decimal.parse(_buyAmountController.text) >
|
||||
// _BuyFormState.maxCrypto) {
|
||||
// _buyAmountController.text = _BuyFormState.maxCrypto.toString();
|
||||
// }
|
||||
// }
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
|
@ -541,7 +571,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
height: 24,
|
||||
),
|
||||
Text(
|
||||
errorMessage,
|
||||
quoteResponse.exception!.errorMessage,
|
||||
style: STextStyles.smallMed14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -603,10 +633,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
level: LogLevel.Warning,
|
||||
);
|
||||
return BuyResponse(
|
||||
exception: BuyException(
|
||||
response.toString(),
|
||||
BuyExceptionType.generic,
|
||||
),
|
||||
exception: response.exception ??
|
||||
BuyException(
|
||||
response.toString(),
|
||||
BuyExceptionType.generic,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -852,7 +883,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
.textFieldDefaultBG,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0, top: 12.0, right: 12.0, bottom: 12.0),
|
||||
left: 12.0,
|
||||
top: 12.0,
|
||||
right: 12.0,
|
||||
bottom: 12.0,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
|
@ -878,7 +913,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
width: 8,
|
||||
),
|
||||
Text(
|
||||
"${selectedFiat?.ticker ?? 'ERR'}",
|
||||
selectedFiat?.ticker ?? 'ERR',
|
||||
style: STextStyles.largeMedium14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -886,7 +921,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${selectedFiat?.name ?? 'Error'}",
|
||||
selectedFiat?.name ?? 'Error',
|
||||
style: STextStyles.largeMedium14(context),
|
||||
),
|
||||
),
|
||||
|
@ -918,12 +953,13 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: buyWithFiat ? "Use crypto amount" : "Use fiat amount",
|
||||
onTap: () {
|
||||
setState(() {
|
||||
buyWithFiat = !buyWithFiat;
|
||||
});
|
||||
validateAmount();
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -938,10 +974,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
key: const Key("buyAmountInputFieldTextFieldKey"),
|
||||
controller: _buyAmountController
|
||||
..text = _BuyFormState.buyWithFiat
|
||||
? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
|
||||
: _BuyFormState.minCrypto.toStringAsFixed(8),
|
||||
controller: _buyAmountController,
|
||||
// note: setting the text value here will set it every time this widget rebuilds
|
||||
// ..text = _BuyFormState.buyWithFiat
|
||||
// ? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
|
||||
// : _BuyFormState.minCrypto.toStringAsFixed(8),
|
||||
focusNode: _buyAmountFocusNode,
|
||||
keyboardType: Util.isDesktop
|
||||
? null
|
||||
|
@ -950,7 +987,10 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
decimal: true,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
inputFormatters: [NumericalRangeFormatter()],
|
||||
// inputFormatters: [NumericalRangeFormatter()],
|
||||
onChanged: (_) {
|
||||
validateAmount();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
// top: 22,
|
||||
|
@ -985,8 +1025,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
child: Text(
|
||||
format.simpleCurrencySymbol(
|
||||
selectedFiat?.ticker ??
|
||||
"ERR".toUpperCase()),
|
||||
selectedFiat?.ticker.toUpperCase() ??
|
||||
"ERR"),
|
||||
textAlign: TextAlign.center,
|
||||
style: STextStyles.smallMed12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
|
@ -1023,18 +1063,20 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
key: const Key(
|
||||
"buyViewClearAmountFieldButtonKey"),
|
||||
onTap: () {
|
||||
if (_BuyFormState.buyWithFiat) {
|
||||
_buyAmountController.text = _BuyFormState
|
||||
.minFiat
|
||||
.toStringAsFixed(2);
|
||||
} else {
|
||||
if (selectedCrypto?.ticker ==
|
||||
_BuyFormState.boundedCryptoTicker) {
|
||||
_buyAmountController.text = _BuyFormState
|
||||
.minCrypto
|
||||
.toStringAsFixed(8);
|
||||
}
|
||||
}
|
||||
// if (_BuyFormState.buyWithFiat) {
|
||||
// _buyAmountController.text = _BuyFormState
|
||||
// .minFiat
|
||||
// .toStringAsFixed(2);
|
||||
// } else {
|
||||
// if (selectedCrypto?.ticker ==
|
||||
// _BuyFormState.boundedCryptoTicker) {
|
||||
// _buyAmountController.text = _BuyFormState
|
||||
// .minCrypto
|
||||
// .toStringAsFixed(8);
|
||||
// }
|
||||
// }
|
||||
_buyAmountController.text = "";
|
||||
validateAmount();
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
|
@ -1051,7 +1093,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
_buyAmountController.text =
|
||||
amountString.toString();
|
||||
|
||||
setState(() {});
|
||||
validateAmount();
|
||||
}
|
||||
},
|
||||
child: _buyAmountController.text.isEmpty
|
||||
|
@ -1064,6 +1106,14 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 10 : 4,
|
||||
),
|
||||
if (_amountOutOfRangeErrorString.isNotEmpty)
|
||||
Text(
|
||||
_amountOutOfRangeErrorString,
|
||||
style: STextStyles.errorSmall(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
|
@ -1079,8 +1129,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
),
|
||||
if (isStackCoin(selectedCrypto?.ticker))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: () {
|
||||
try {
|
||||
final coin = coinFromTickerCaseInsensitive(
|
||||
|
@ -1103,7 +1153,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
_receiveAddressController.text =
|
||||
await manager.currentReceivingAddress;
|
||||
|
||||
setState(() {});
|
||||
setState(() {
|
||||
_addressToggleFlag =
|
||||
_receiveAddressController.text.isNotEmpty;
|
||||
});
|
||||
validateAmount();
|
||||
}
|
||||
});
|
||||
} catch (e, s) {
|
||||
|
@ -1171,7 +1225,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
_receiveAddressController.text = "";
|
||||
_address = "";
|
||||
setState(() {
|
||||
_addressToggleFlag = true;
|
||||
_addressToggleFlag = false;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
|
@ -1342,27 +1396,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (_receiveAddressController.text.isNotEmpty &&
|
||||
_buyAmountController.text.isNotEmpty) {
|
||||
previewQuote(quote);
|
||||
}
|
||||
},
|
||||
child: PrimaryButton(
|
||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||
enabled: _receiveAddressController.text.isNotEmpty &&
|
||||
_buyAmountController.text.isNotEmpty,
|
||||
onPressed: () {
|
||||
if (_receiveAddressController.text.isNotEmpty &&
|
||||
_buyAmountController.text.isNotEmpty) {
|
||||
previewQuote(quote);
|
||||
}
|
||||
},
|
||||
label: "Preview quote",
|
||||
)),
|
||||
PrimaryButton(
|
||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||
enabled: _addressToggleFlag &&
|
||||
_amountOutOfRangeErrorString.isEmpty &&
|
||||
_buyAmountController.text.isNotEmpty,
|
||||
onPressed: () {
|
||||
previewQuote(quote);
|
||||
},
|
||||
label: "Preview quote",
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -1371,51 +1413,53 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
}
|
||||
|
||||
// See https://stackoverflow.com/a/68072967
|
||||
class NumericalRangeFormatter extends TextInputFormatter {
|
||||
NumericalRangeFormatter();
|
||||
// might need this again in the future
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
TextSelection newSelection = newValue.selection;
|
||||
String newVal = _BuyFormState.buyWithFiat
|
||||
? Decimal.parse(newValue.text).toStringAsFixed(2)
|
||||
: Decimal.parse(newValue.text).toStringAsFixed(8);
|
||||
if (newValue.text == '') {
|
||||
return newValue;
|
||||
} else {
|
||||
if (_BuyFormState.buyWithFiat) {
|
||||
if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
|
||||
newVal = _BuyFormState.minFiat.toStringAsFixed(2);
|
||||
// _BuyFormState._buyAmountController.selection =
|
||||
// TextSelection.collapsed(
|
||||
// offset: _BuyFormState.buyWithFiat
|
||||
// ? _BuyFormState._buyAmountController.text.length - 2
|
||||
// : _BuyFormState._buyAmountController.text.length - 8);
|
||||
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
|
||||
newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
|
||||
}
|
||||
} else if (!_BuyFormState.buyWithFiat &&
|
||||
_BuyFormState.selectedCrypto?.ticker ==
|
||||
_BuyFormState.boundedCryptoTicker) {
|
||||
if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
|
||||
newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
|
||||
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
|
||||
newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final regexString = _BuyFormState.buyWithFiat
|
||||
? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
|
||||
: r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
|
||||
|
||||
// return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
|
||||
return RegExp(regexString).hasMatch(newVal)
|
||||
? TextEditingValue(text: newVal, selection: newSelection)
|
||||
: oldValue;
|
||||
}
|
||||
}
|
||||
// // See https://stackoverflow.com/a/68072967
|
||||
// class NumericalRangeFormatter extends TextInputFormatter {
|
||||
// NumericalRangeFormatter();
|
||||
//
|
||||
// @override
|
||||
// TextEditingValue formatEditUpdate(
|
||||
// TextEditingValue oldValue,
|
||||
// TextEditingValue newValue,
|
||||
// ) {
|
||||
// TextSelection newSelection = newValue.selection;
|
||||
// String newVal = _BuyFormState.buyWithFiat
|
||||
// ? Decimal.parse(newValue.text).toStringAsFixed(2)
|
||||
// : Decimal.parse(newValue.text).toStringAsFixed(8);
|
||||
// if (newValue.text == '') {
|
||||
// return newValue;
|
||||
// } else {
|
||||
// if (_BuyFormState.buyWithFiat) {
|
||||
// if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
|
||||
// newVal = _BuyFormState.minFiat.toStringAsFixed(2);
|
||||
// // _BuyFormState._buyAmountController.selection =
|
||||
// // TextSelection.collapsed(
|
||||
// // offset: _BuyFormState.buyWithFiat
|
||||
// // ? _BuyFormState._buyAmountController.text.length - 2
|
||||
// // : _BuyFormState._buyAmountController.text.length - 8);
|
||||
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
|
||||
// newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
|
||||
// }
|
||||
// } else if (!_BuyFormState.buyWithFiat &&
|
||||
// _BuyFormState.selectedCrypto?.ticker ==
|
||||
// _BuyFormState.boundedCryptoTicker) {
|
||||
// if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
|
||||
// newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
|
||||
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
|
||||
// newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// final regexString = _BuyFormState.buyWithFiat
|
||||
// ? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
|
||||
// : r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
|
||||
//
|
||||
// // return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
|
||||
// return RegExp(regexString).hasMatch(newVal)
|
||||
// ? TextEditingValue(text: newVal, selection: newSelection)
|
||||
// : oldValue;
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -189,8 +189,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
if (isStackCoin(model.receiveTicker))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: () {
|
||||
try {
|
||||
final coin =
|
||||
|
@ -448,8 +448,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
if (isStackCoin(model.sendTicker))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: () {
|
||||
try {
|
||||
final coin =
|
||||
|
|
|
@ -516,7 +516,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "View transaction",
|
||||
onTap: () {
|
||||
final Coin coin =
|
||||
|
|
|
@ -50,7 +50,7 @@ class _HomeViewState extends ConsumerState<HomeView> {
|
|||
|
||||
Future<bool> _onWillPop() async {
|
||||
// go to home view when tapping back on the main exchange view
|
||||
if (ref.read(homeViewPageIndexStateProvider.state).state == 1) {
|
||||
if (ref.read(homeViewPageIndexStateProvider.state).state != 0) {
|
||||
ref.read(homeViewPageIndexStateProvider.state).state = 0;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -176,14 +176,49 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
children: [
|
||||
PayNymBot(
|
||||
paymentCodeString: widget.accountLite.code,
|
||||
size: 32,
|
||||
size: 36,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.w600_12(context),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
FutureBuilder(
|
||||
future:
|
||||
wallet.hasConnected(widget.accountLite.code),
|
||||
builder: (context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.data == true) {
|
||||
return Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Connected",
|
||||
style: STextStyles.w500_12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -195,33 +230,33 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
if (snapshot.data!) {
|
||||
return PrimaryButton(
|
||||
label: "Send",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleArrowUpRight,
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 14,
|
||||
height: 14,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 4,
|
||||
width: 86,
|
||||
iconSpacing: 8,
|
||||
width: 100,
|
||||
onPressed: _onSend,
|
||||
);
|
||||
} else {
|
||||
return PrimaryButton(
|
||||
label: "Connect",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circlePlusFilled,
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 13,
|
||||
height: 13,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 4,
|
||||
width: 86,
|
||||
iconSpacing: 8,
|
||||
width: 128,
|
||||
onPressed: _onConnectPressed,
|
||||
);
|
||||
}
|
||||
|
@ -256,6 +291,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.warningForeground,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -286,7 +322,9 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
children: [
|
||||
Text(
|
||||
"PayNym address",
|
||||
style: STextStyles.infoSmall(context),
|
||||
style: STextStyles.infoSmall(context).copyWith(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
|
@ -297,6 +335,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -311,7 +350,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
),
|
||||
QrImage(
|
||||
padding: const EdgeInsets.all(0),
|
||||
size: 86,
|
||||
size: 100,
|
||||
data: widget.accountLite.code,
|
||||
foregroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.textDark,
|
||||
|
@ -340,16 +379,16 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Copy",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
iconSpacing: 4,
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
|
|
|
@ -56,7 +56,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
children: [
|
||||
PayNymBot(
|
||||
paymentCodeString: paynymAccount.codes.first.code,
|
||||
size: isDesktop ? 56 : 32,
|
||||
size: isDesktop ? 56 : 36,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
|
@ -65,7 +65,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
paynymAccount.nymName,
|
||||
style: isDesktop
|
||||
? STextStyles.w500_24(context)
|
||||
: STextStyles.w600_12(context),
|
||||
: STextStyles.w600_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -87,7 +87,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
children: [
|
||||
Expanded(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 107),
|
||||
constraints: const BoxConstraints(minHeight: 130),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@ -100,7 +100,9 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.infoSmall(context),
|
||||
: STextStyles.infoSmall(context).copyWith(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
|
@ -113,14 +115,15 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Copy",
|
||||
textSize: isDesktop ? 18 : 10,
|
||||
textSize: isDesktop ? 18 : 14,
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
|
@ -146,7 +149,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
),
|
||||
QrImage(
|
||||
padding: const EdgeInsets.all(0),
|
||||
size: 107,
|
||||
size: 130,
|
||||
data: paynymAccount.codes.first.code,
|
||||
foregroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.textDark,
|
||||
|
|
|
@ -117,7 +117,7 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
|||
),
|
||||
Image(
|
||||
image: AssetImage(
|
||||
Assets.png.stack,
|
||||
Assets.png.unclaimedPaynym,
|
||||
),
|
||||
width: MediaQuery.of(context).size.width / 2,
|
||||
),
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/dialogs/paynym_qr_popup.dart';
|
||||
|
@ -302,7 +303,9 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
.code,
|
||||
12,
|
||||
5),
|
||||
style: STextStyles.label(context),
|
||||
style: STextStyles.label(context).copyWith(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 11,
|
||||
|
@ -312,11 +315,11 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Copy",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: CopyIcon(
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
|
@ -349,17 +352,34 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Share",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: ShareIcon(
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
onPressed: () {
|
||||
// copy to clipboard
|
||||
onPressed: () async {
|
||||
Rect? sharePositionOrigin;
|
||||
if (await Util.isIPad) {
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
if (box != null) {
|
||||
sharePositionOrigin =
|
||||
box.localToGlobal(Offset.zero) & box.size;
|
||||
}
|
||||
}
|
||||
|
||||
await Share.share(
|
||||
ref
|
||||
.read(myPaynymAccountStateProvider.state)
|
||||
.state!
|
||||
.codes
|
||||
.first
|
||||
.code,
|
||||
sharePositionOrigin: sharePositionOrigin);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -369,11 +389,11 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Address",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: QrCodeIcon(
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
|
@ -536,7 +556,7 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
child: child,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: isDesktop ? 56 : 40,
|
||||
height: isDesktop ? 56 : 48,
|
||||
width: isDesktop ? 490 : null,
|
||||
child: Toggle(
|
||||
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
|
|
@ -171,14 +171,47 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
children: [
|
||||
PayNymBot(
|
||||
paymentCodeString: widget.accountLite.code,
|
||||
size: 32,
|
||||
size: 36,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.desktopTextSmall(context),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.desktopTextSmall(context),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: wallet.hasConnected(widget.accountLite.code),
|
||||
builder: (context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.data == true) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Connected",
|
||||
style: STextStyles.desktopTextSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -324,7 +357,7 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Copy",
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
|
|
|
@ -37,7 +37,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
|||
child: Row(
|
||||
children: [
|
||||
PayNymBot(
|
||||
size: 32,
|
||||
size: 36,
|
||||
paymentCodeString: widget.paymentCodeString,
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -56,7 +56,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
|||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
)
|
||||
: STextStyles.w500_12(context),
|
||||
: STextStyles.w500_14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
|
@ -65,7 +65,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
|||
Format.shorten(widget.paymentCodeString, 12, 5),
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
: STextStyles.w500_12(context).copyWith(
|
||||
: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
|
|
|
@ -77,7 +77,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
|||
child: Row(
|
||||
children: [
|
||||
PayNymBot(
|
||||
size: 32,
|
||||
size: 36,
|
||||
paymentCodeString: widget.accountLite.code,
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -96,7 +96,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
|||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
)
|
||||
: STextStyles.w500_12(context),
|
||||
: STextStyles.w500_14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
|
@ -105,7 +105,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
|||
Format.shorten(widget.accountLite.code, 12, 5),
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
: STextStyles.w500_12(context).copyWith(
|
||||
: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
|
|
|
@ -7,10 +7,12 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/receiving_addresses_view.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
|
@ -19,6 +21,7 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class ReceiveView extends ConsumerStatefulWidget {
|
||||
const ReceiveView({
|
||||
|
@ -128,6 +131,84 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
"Receive ${coin.ticker}",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
key: const Key("walletNetworkSettingsAddNewNodeViewButton"),
|
||||
size: 36,
|
||||
shadows: const [],
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.verticalEllipsis,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<dynamic>(
|
||||
barrierColor: Colors.transparent,
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 9,
|
||||
right: 10,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
// boxShadow: [CFColors.standardBoxShadow],
|
||||
boxShadow: const [],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed(
|
||||
ReceivingAddressesView.routeName,
|
||||
arguments: Tuple2(walletId, false),
|
||||
);
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
"Address list",
|
||||
style: STextStyles.baseXS(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
|
@ -233,7 +314,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Create new QR code",
|
||||
onTap: () async {
|
||||
unawaited(Navigator.of(context).push(
|
||||
|
|
114
lib/pages/receive_view/receiving_addresses_view.dart
Normal file
114
lib/pages/receive_view/receiving_addresses_view.dart
Normal file
|
@ -0,0 +1,114 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class ReceivingAddressesView extends ConsumerWidget {
|
||||
const ReceivingAddressesView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.isDesktop,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/receivingAddressesView";
|
||||
|
||||
final String walletId;
|
||||
final bool isDesktop;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Receiving addresses",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: MainDB.instance
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findAll(),
|
||||
builder: (context, AsyncSnapshot<List<Address>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.data != null) {
|
||||
// listview
|
||||
return ListView.separated(
|
||||
itemCount: snapshot.data!.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 10,
|
||||
),
|
||||
itemBuilder: (_, index) => AddressCard(
|
||||
address: snapshot.data![index],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: LoadingIndicator(
|
||||
height: 200,
|
||||
width: 200,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressCard extends StatelessWidget {
|
||||
const AddressCard({
|
||||
Key? key,
|
||||
required this.address,
|
||||
}) : super(key: key);
|
||||
|
||||
final Address address;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
address.value,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1304,7 +1304,7 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
style: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Send all ${coin.ticker}",
|
||||
onTap: () async {
|
||||
if (coin == Coin.firo ||
|
||||
|
|
|
@ -484,7 +484,7 @@ class AboutView extends ConsumerWidget {
|
|||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "https://stackwallet.com",
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
|
|
|
@ -282,7 +282,7 @@ class _DebugViewState extends ConsumerState<DebugView> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Save Debug Info to clipboard",
|
||||
onTap: () async {
|
||||
try {
|
||||
|
@ -350,7 +350,7 @@ class _DebugViewState extends ConsumerState<DebugView> {
|
|||
},
|
||||
),
|
||||
const Spacer(),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Save logs to file",
|
||||
onTap: () async {
|
||||
final systemfile = SWBFileSystem();
|
||||
|
|
|
@ -92,7 +92,7 @@ class _CoinNodesViewState extends ConsumerState<CoinNodesView> {
|
|||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new node",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -327,7 +327,7 @@ class _AutoBackupViewState extends ConsumerState<AutoBackupView> {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Back up now",
|
||||
onTap: () {
|
||||
ref.read(autoSWBServiceProvider).doBackup();
|
||||
|
@ -448,7 +448,7 @@ class _AutoBackupViewState extends ConsumerState<AutoBackupView> {
|
|||
height: 20,
|
||||
),
|
||||
Center(
|
||||
child: BlueTextButton(
|
||||
child: CustomTextButton(
|
||||
text: "Edit Auto Backup",
|
||||
onTap: () async {
|
||||
Navigator.of(context)
|
||||
|
|
|
@ -749,7 +749,7 @@ class _WalletNetworkSettingsViewState
|
|||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
: STextStyles.smallMed12(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new node",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
@ -880,7 +880,7 @@ class _WalletNetworkSettingsViewState
|
|||
top: 16,
|
||||
bottom: 6,
|
||||
),
|
||||
child: BlueTextButton(
|
||||
child: CustomTextButton(
|
||||
text: "Rescan",
|
||||
onTap: () async {
|
||||
await Navigator.of(context).push(
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
|
@ -21,6 +20,8 @@ import 'package:stackwallet/widgets/trade_card.dart';
|
|||
import 'package:stackwallet/widgets/transaction_card.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../utilities/enums/coin_enum.dart';
|
||||
|
||||
class TransactionsList extends ConsumerStatefulWidget {
|
||||
const TransactionsList({
|
||||
Key? key,
|
||||
|
@ -68,11 +69,18 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
BuildContext context,
|
||||
Transaction tx,
|
||||
BorderRadius? radius,
|
||||
Coin coin,
|
||||
) {
|
||||
final matchingTrades = ref
|
||||
.read(tradesServiceProvider)
|
||||
.trades
|
||||
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
|
||||
|
||||
final isConfirmed = tx.isConfirmed(
|
||||
ref.watch(
|
||||
widget.managerProvider.select((value) => value.currentHeight)),
|
||||
coin.requiredConfirmations);
|
||||
|
||||
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
|
||||
final trade = matchingTrades.first;
|
||||
return Container(
|
||||
|
@ -85,7 +93,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
children: [
|
||||
TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||
key: isConfirmed
|
||||
? Key(tx.txid + tx.type.name + tx.address.value.toString())
|
||||
: UniqueKey(), //
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
|
@ -180,7 +190,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
),
|
||||
child: TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||
key: isConfirmed
|
||||
? Key(tx.txid + tx.type.name + tx.address.value.toString())
|
||||
: UniqueKey(),
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
|
@ -188,13 +200,6 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
}
|
||||
}
|
||||
|
||||
void updateHeightProvider(Manager manager) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
ref.read(currentHeightProvider(manager.coin).state).state =
|
||||
manager.currentHeight;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
managerProvider = widget.managerProvider;
|
||||
|
@ -203,14 +208,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final managerProvider = ref
|
||||
// .watch(walletsChangeNotifierProvider)
|
||||
// .getManagerProvider(widget.walletId);
|
||||
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId)));
|
||||
|
||||
updateHeightProvider(manager);
|
||||
return FutureBuilder(
|
||||
future: manager.transactions,
|
||||
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {
|
||||
|
@ -264,7 +264,7 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = _transactions2[index];
|
||||
return itemBuilder(context, tx, radius);
|
||||
return itemBuilder(context, tx, radius, manager.coin);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return Container(
|
||||
|
@ -291,7 +291,7 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = _transactions2[index];
|
||||
return itemBuilder(context, tx, radius);
|
||||
return itemBuilder(context, tx, radius, manager.coin);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -175,7 +175,18 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
children: [
|
||||
Text(
|
||||
"Paynym",
|
||||
style: STextStyles.w600_12(context),
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.robotHead,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.bottomNavIconIcon,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -360,62 +371,6 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).hasPaynymSupport)))
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 66,
|
||||
),
|
||||
onPressed: () {
|
||||
if (scale == 0) {
|
||||
setState(() {
|
||||
scale = 1;
|
||||
});
|
||||
} else if (scale == 1) {
|
||||
setState(() {
|
||||
scale = 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
splashColor:
|
||||
Theme.of(context).extension<StackColors>()!.highlight,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
widget.height / 2.0,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.bars,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Text(
|
||||
"More",
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
if (widget.coin.hasBuySupport)
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -455,10 +410,65 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (widget.coin.hasBuySupport)
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).hasPaynymSupport)))
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 66,
|
||||
),
|
||||
onPressed: () {
|
||||
if (scale == 0) {
|
||||
setState(() {
|
||||
scale = 1;
|
||||
});
|
||||
} else if (scale == 1) {
|
||||
setState(() {
|
||||
scale = 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
splashColor:
|
||||
Theme.of(context).extension<StackColors>()!.highlight,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
widget.height / 2.0,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.bars,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.bottomNavIconIcon,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Text(
|
||||
"More",
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -10,7 +10,6 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
|
|||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||
|
@ -131,7 +130,9 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
// check if address book name contains
|
||||
contains |= contacts
|
||||
.where((e) =>
|
||||
e.addresses.where((a) => a.address == tx.address).isNotEmpty &&
|
||||
e.addresses
|
||||
.where((a) => a.address == tx.address.value?.value)
|
||||
.isNotEmpty &&
|
||||
e.name.toLowerCase().contains(keyword))
|
||||
.isNotEmpty;
|
||||
|
||||
|
@ -853,7 +854,8 @@ class _DesktopTransactionCardRowState
|
|||
prefix = "";
|
||||
}
|
||||
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
|
|
@ -11,7 +11,6 @@ import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
|||
import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||
|
@ -298,7 +297,8 @@ class _TransactionDetailsViewState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
|
@ -1092,7 +1092,7 @@ class _TransactionDetailsViewState
|
|||
height: 8,
|
||||
),
|
||||
if (coin != Coin.epicCash)
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Open in block explorer",
|
||||
onTap: () async {
|
||||
final uri =
|
||||
|
|
|
@ -649,7 +649,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
.textDark3,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
@ -774,9 +774,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
onBuyPressed: () {
|
||||
// TODO set default coin to currently open wallet here by passing it as an argument
|
||||
// final coin = ref.read(managerProvider).coin;
|
||||
|
||||
unawaited(Navigator.of(context).pushNamed(
|
||||
BuyInWalletView.routeName,
|
||||
arguments: coin,
|
||||
|
|
|
@ -24,7 +24,7 @@ class AllWallets extends StatelessWidget {
|
|||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(AddWalletView.routeName);
|
||||
|
|
|
@ -66,7 +66,7 @@ class DesktopAddressCard extends StatelessWidget {
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Copy",
|
||||
onTap: () {
|
||||
clipboard.setData(
|
||||
|
@ -87,7 +87,7 @@ class DesktopAddressCard extends StatelessWidget {
|
|||
if (contactId != "default")
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
return BlueTextButton(
|
||||
return CustomTextButton(
|
||||
text: "Edit",
|
||||
onTap: () async {
|
||||
ref.refresh(
|
||||
|
|
|
@ -182,7 +182,7 @@ class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> {
|
|||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new",
|
||||
onTap: () async {
|
||||
ref.refresh(
|
||||
|
|
|
@ -300,8 +300,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
|
|||
),
|
||||
if (isStackCoin(ref.watch(desktopExchangeModelProvider
|
||||
.select((value) => value!.receiveTicker))))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: selectRecipientAddressFromStack,
|
||||
),
|
||||
],
|
||||
|
@ -432,8 +432,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
|
|||
),
|
||||
if (isStackCoin(ref.watch(desktopExchangeModelProvider
|
||||
.select((value) => value!.sendTicker))))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: selectRefundAddressFromStack,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -221,7 +221,7 @@ class _DesktopChooseFromStackState
|
|||
const SizedBox(
|
||||
width: 80,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Select wallet",
|
||||
onTap: () async {
|
||||
final address =
|
||||
|
|
|
@ -72,7 +72,7 @@ class _DesktopTradeHistoryState extends ConsumerState<DesktopTradeHistory> {
|
|||
"Recent trades",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
|
|
|
@ -39,7 +39,7 @@ class DesktopFavoriteWallets extends ConsumerWidget {
|
|||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Edit",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(ManageFavoritesView.routeName);
|
||||
|
|
|
@ -38,7 +38,7 @@ class _MyWalletsState extends ConsumerState<MyWallets> {
|
|||
),
|
||||
),
|
||||
const Spacer(),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new wallet",
|
||||
onTap: () {
|
||||
Navigator.of(
|
||||
|
|
|
@ -133,7 +133,7 @@ class _ContactListItemState extends ConsumerState<ContactListItem> {
|
|||
],
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Select wallet",
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(e);
|
||||
|
|
|
@ -987,7 +987,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Send all ${coin.ticker}",
|
||||
onTap: sendAllTapped,
|
||||
),
|
||||
|
|
|
@ -37,7 +37,7 @@ class _RecentDesktopTransactionsState
|
|||
.textFieldActiveSearchIconLeft,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -261,7 +261,7 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
|||
const SizedBox(
|
||||
height: 60,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Forgot password?",
|
||||
textSize: 20,
|
||||
onTap: () {
|
||||
|
|
|
@ -450,7 +450,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> {
|
|||
STextStyles.itemSubtitle(
|
||||
context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Back up now",
|
||||
onTap: () {
|
||||
ref
|
||||
|
|
|
@ -678,7 +678,7 @@ class DesktopAboutView extends ConsumerWidget {
|
|||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text:
|
||||
"https://stackwallet.com",
|
||||
onTap: () {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
import 'package:dart_numerics/dart_numerics.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
final currentHeightProvider =
|
||||
StateProvider.family<int, Coin>((ref, coin) => int64MaxValue);
|
|
@ -46,6 +46,7 @@ import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
|||
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/receive_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/receiving_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/send_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart';
|
||||
|
@ -820,6 +821,21 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case ReceivingAddressesView.routeName:
|
||||
if (args is Tuple2<String, bool>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => ReceivingAddressesView(
|
||||
walletId: args.item1,
|
||||
isDesktop: args.item2,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case SendView.routeName:
|
||||
if (args is Tuple2<String, Coin>) {
|
||||
return getRoute(
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
enum BuyExceptionType { generic, serializeResponseError }
|
||||
enum BuyExceptionType {
|
||||
generic,
|
||||
serializeResponseError,
|
||||
cryptoAmountOutOfRange,
|
||||
}
|
||||
|
||||
class BuyException implements Exception {
|
||||
String errorMessage;
|
||||
|
|
|
@ -210,7 +210,20 @@ class SimplexAPI {
|
|||
|
||||
BuyResponse<SimplexQuote> _parseQuote(dynamic jsonArray) {
|
||||
try {
|
||||
String cryptoAmount = "${jsonArray['digital_money']['amount']}";
|
||||
// final Map<String, dynamic> lol =
|
||||
// Map<String, dynamic>.from(jsonArray as Map);
|
||||
|
||||
double? cryptoAmount = jsonArray['digital_money']?['amount'] as double?;
|
||||
|
||||
if (cryptoAmount == null) {
|
||||
String error = jsonArray['error'] as String;
|
||||
return BuyResponse(
|
||||
exception: BuyException(
|
||||
error,
|
||||
BuyExceptionType.cryptoAmountOutOfRange,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SimplexQuote quote = jsonArray['quote'] as SimplexQuote;
|
||||
final SimplexQuote _quote = SimplexQuote(
|
||||
|
@ -277,9 +290,9 @@ class SimplexAPI {
|
|||
}
|
||||
final jsonArray = jsonDecode(res.body); // TODO check if valid json
|
||||
if (jsonArray.containsKey('error') as bool) {
|
||||
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
|
||||
throw Exception(jsonArray['message']);
|
||||
}
|
||||
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
|
||||
throw Exception(jsonArray['message']);
|
||||
}
|
||||
}
|
||||
|
||||
SimplexOrder _order = SimplexOrder(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:bech32/bech32.dart';
|
||||
import 'package:bip32/bip32.dart' as bip32;
|
||||
|
@ -40,6 +41,7 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
|||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/paynym_is_api.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
@ -218,6 +220,19 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
Future<String> get currentChangeAddressP2PKH async =>
|
||||
(await _currentChangeAddressP2PKH).value;
|
||||
|
||||
Future<isar_models.Address> get _currentChangeAddressP2PKH async =>
|
||||
(await db
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.typeEqualTo(isar_models.AddressType.p2pkh)
|
||||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip44);
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
_hasCalledExit = true;
|
||||
|
@ -251,6 +266,14 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -683,16 +706,28 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
...p2shChangeAddressArray,
|
||||
]);
|
||||
|
||||
// generate to ensure notification address is in db before refreshing transactions
|
||||
await getMyNotificationAddress(DerivePathType.bip44);
|
||||
// get own payment code
|
||||
final myCode = await getPaymentCode(DerivePathType.bip44);
|
||||
|
||||
// refresh transactions to pick up any received notification transactions
|
||||
await _refreshTransactions();
|
||||
|
||||
final Set<String> codesToCheck = {};
|
||||
final nym = await PaynymIsApi().nym(myCode.toString());
|
||||
if (nym.value != null) {
|
||||
for (final follower in nym.value!.followers) {
|
||||
codesToCheck.add(follower.code);
|
||||
}
|
||||
for (final following in nym.value!.following) {
|
||||
codesToCheck.add(following.code);
|
||||
}
|
||||
}
|
||||
|
||||
// restore paynym transactions
|
||||
await restoreAllHistory(
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
paymentCodeStrings: codesToCheck,
|
||||
);
|
||||
|
||||
await _updateUTXOs();
|
||||
|
@ -919,6 +954,17 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
|
||||
final myCode = await getPaymentCode(DerivePathType.bip44);
|
||||
final Set<String> codesToCheck = {};
|
||||
final nym = await PaynymIsApi().nym(myCode.toString());
|
||||
if (nym.value != null) {
|
||||
for (final follower in nym.value!.followers) {
|
||||
codesToCheck.add(follower.code);
|
||||
}
|
||||
for (final following in nym.value!.following) {
|
||||
codesToCheck.add(following.code);
|
||||
}
|
||||
}
|
||||
|
||||
final currentHeight = await chainHeight;
|
||||
const storedHeight = 1; //await storedChainHeight;
|
||||
|
@ -935,6 +981,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
||||
await _checkCurrentReceivingAddressesForTransactions();
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.4, walletId));
|
||||
await checkAllCurrentReceivingPaynymAddressesForTransactions();
|
||||
|
||||
final fetchFuture = _refreshTransactions();
|
||||
|
@ -955,6 +1002,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
.fire(RefreshPercentChangedEvent(0.80, walletId));
|
||||
|
||||
await fetchFuture;
|
||||
await checkForNotificationTransactionsTo(codesToCheck);
|
||||
await getAllTxsToWatch();
|
||||
GlobalEventBus.instance
|
||||
.fire(RefreshPercentChangedEvent(0.90, walletId));
|
||||
|
@ -1305,15 +1353,18 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
secureStorage: secureStore,
|
||||
getMnemonic: () => mnemonic,
|
||||
getChainHeight: () => chainHeight,
|
||||
getCurrentChangeAddress: () => currentChangeAddress,
|
||||
getCurrentChangeAddress: () => currentChangeAddressP2PKH,
|
||||
estimateTxFee: estimateTxFee,
|
||||
prepareSend: prepareSend,
|
||||
getTxCount: getTxCount,
|
||||
fetchBuildTxData: fetchBuildTxData,
|
||||
refresh: refresh,
|
||||
checkChangeAddressForTransactions: _checkChangeAddressForTransactions,
|
||||
checkChangeAddressForTransactions:
|
||||
_checkP2PKHChangeAddressForTransactions,
|
||||
addDerivation: addDerivation,
|
||||
addDerivations: addDerivations,
|
||||
dustLimitP2PKH: DUST_LIMIT_P2PKH,
|
||||
minConfirms: MINIMUM_CONFIRMATIONS,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1809,7 +1860,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -1956,6 +2007,50 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _checkP2PKHChangeAddressForTransactions() async {
|
||||
try {
|
||||
final currentChange = await _currentChangeAddressP2PKH;
|
||||
final int txCount = await getTxCount(address: currentChange.value);
|
||||
Logging.instance.log(
|
||||
'Number of txs for current change address $currentChange: $txCount',
|
||||
level: LogLevel.Info);
|
||||
|
||||
if (txCount >= 1 || currentChange.derivationIndex < 0) {
|
||||
// First increment the change index
|
||||
final newChangeIndex = currentChange.derivationIndex + 1;
|
||||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip44);
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(newChangeAddress.value)
|
||||
.findFirst();
|
||||
if (existing == null) {
|
||||
// Add that new change address
|
||||
await db.putAddress(newChangeAddress);
|
||||
} else {
|
||||
// we need to update the address
|
||||
await db.updateAddress(existing, newChangeAddress);
|
||||
}
|
||||
// keep checking until address with no tx history is set as current
|
||||
await _checkP2PKHChangeAddressForTransactions();
|
||||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _checkCurrentReceivingAddressesForTransactions() async {
|
||||
try {
|
||||
// for (final type in DerivePathType.values) {
|
||||
|
@ -2185,6 +2280,8 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
Logging.instance.log("availableOutputs.length: ${availableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
Logging.instance
|
||||
.log("spendableOutputs: $spendableOutputs", level: LogLevel.Info);
|
||||
Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue",
|
||||
|
@ -2277,24 +2374,38 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
return transactionObject;
|
||||
}
|
||||
|
||||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
satoshisBeingUsed - satoshiAmountToSend - 1
|
||||
], // dust limit is the minimum amount a change output should be
|
||||
))["vSize"] as int;
|
||||
final int vSizeForOneOutput;
|
||||
try {
|
||||
vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
} catch (e) {
|
||||
Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final int vSizeForTwoOutPuts;
|
||||
try {
|
||||
vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
max(0, satoshisBeingUsed - satoshiAmountToSend - 1),
|
||||
],
|
||||
))["vSize"] as int;
|
||||
} catch (e) {
|
||||
Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
// Assume 1 output, only for recipient and no change
|
||||
final feeForOneOutput = estimateTxFee(
|
||||
|
|
|
@ -217,6 +217,14 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -1665,7 +1673,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -2593,13 +2601,12 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final n = output["n"];
|
||||
if (n != null && n == utxosToUse[i].vout) {
|
||||
String address = output["scriptPubKey"]["addresses"][0] as String;
|
||||
if (bitbox.Address.detectFormat(address) ==
|
||||
if (bitbox.Address.detectFormat(address) !=
|
||||
bitbox.Address.formatCashAddr) {
|
||||
if (validateCashAddr(address)) {
|
||||
address = bitbox.Address.toLegacyAddress(address);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unsupported address found during fetchBuildTxData(): $address");
|
||||
try {
|
||||
address = bitbox.Address.toCashAddress(address);
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
if (!addressTxid.containsKey(address)) {
|
||||
|
|
|
@ -226,6 +226,14 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -1612,7 +1620,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
|
|
@ -1416,6 +1416,14 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
});
|
||||
|
||||
await updateCachedChainHeight(latestHeight!);
|
||||
if (latestHeight! > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return latestHeight!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -3665,7 +3665,7 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -4897,6 +4897,14 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -241,6 +241,14 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -1776,7 +1784,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
|
|
@ -236,6 +236,14 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -1755,7 +1763,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
|
|
@ -231,6 +231,14 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -1651,7 +1659,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
|
|
@ -189,14 +189,16 @@ mixin ElectrumXParsing {
|
|||
|
||||
TransactionSubType txSubType = TransactionSubType.none;
|
||||
if (this is PaynymWalletInterface && outs.length > 1 && ins.isNotEmpty) {
|
||||
List<String>? scriptChunks = outs[1].scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
for (int i = 0; i < outs.length; i++) {
|
||||
List<String>? scriptChunks = outs[i].scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
|
||||
// https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||
if (bytes.length == 80 && bytes.first == 1) {
|
||||
txSubType = TransactionSubType.bip47Notification;
|
||||
// https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||
if (bytes.length == 80 && bytes.first == 1) {
|
||||
txSubType = TransactionSubType.bip47Notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
|||
import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart';
|
||||
import 'package:stackwallet/exceptions/wallet/paynym_send_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/utilities/bip32_utils.dart';
|
||||
import 'package:stackwallet/utilities/bip47_utils.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
|
@ -35,6 +35,8 @@ mixin PaynymWalletInterface {
|
|||
late final MainDB _db;
|
||||
late final ElectrumX _electrumXClient;
|
||||
late final SecureStorageInterface _secureStorage;
|
||||
late final int _dustLimitP2PKH;
|
||||
late final int _minConfirms;
|
||||
|
||||
// passed in wallet functions
|
||||
late final Future<List<String>> Function() _getMnemonic;
|
||||
|
@ -79,6 +81,8 @@ mixin PaynymWalletInterface {
|
|||
required MainDB db,
|
||||
required ElectrumX electrumXClient,
|
||||
required SecureStorageInterface secureStorage,
|
||||
required int dustLimitP2PKH,
|
||||
required int minConfirms,
|
||||
required Future<List<String>> Function() getMnemonic,
|
||||
required Future<int> Function() getChainHeight,
|
||||
required Future<String> Function() getCurrentChangeAddress,
|
||||
|
@ -125,6 +129,8 @@ mixin PaynymWalletInterface {
|
|||
_db = db;
|
||||
_electrumXClient = electrumXClient;
|
||||
_secureStorage = secureStorage;
|
||||
_dustLimitP2PKH = dustLimitP2PKH;
|
||||
_minConfirms = minConfirms;
|
||||
_getMnemonic = getMnemonic;
|
||||
_getChainHeight = getChainHeight;
|
||||
_getCurrentChangeAddress = getCurrentChangeAddress;
|
||||
|
@ -142,21 +148,33 @@ mixin PaynymWalletInterface {
|
|||
btc_dart.NetworkType get networkType => _network;
|
||||
|
||||
Future<Address> currentReceivingPaynymAddress(PaymentCode sender) async {
|
||||
final key = await lookupKey(sender.toString());
|
||||
final keys = await lookupKey(sender.toString());
|
||||
final address = await _db
|
||||
.getAddresses(_walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymReceive)
|
||||
.and()
|
||||
.otherDataEqualTo(key)
|
||||
.and()
|
||||
.otherDataIsNotNull()
|
||||
.anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e))
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst();
|
||||
|
||||
if (address == null) {
|
||||
final generatedAddress = await _generatePaynymReceivingAddress(sender, 0);
|
||||
await _db.putAddress(generatedAddress);
|
||||
|
||||
final existing = await _db
|
||||
.getAddresses(_walletId)
|
||||
.filter()
|
||||
.valueEqualTo(generatedAddress.value)
|
||||
.findFirst();
|
||||
|
||||
if (existing == null) {
|
||||
// Add that new address
|
||||
await _db.putAddress(generatedAddress);
|
||||
} else {
|
||||
// we need to update the address
|
||||
await _db.updateAddress(existing, generatedAddress);
|
||||
}
|
||||
|
||||
return currentReceivingPaynymAddress(sender);
|
||||
} else {
|
||||
return address;
|
||||
|
@ -197,7 +215,22 @@ mixin PaynymWalletInterface {
|
|||
sender,
|
||||
address.derivationIndex + 1,
|
||||
);
|
||||
await _db.putAddress(nextAddress);
|
||||
|
||||
final existing = await _db
|
||||
.getAddresses(_walletId)
|
||||
.filter()
|
||||
.valueEqualTo(nextAddress.value)
|
||||
.findFirst();
|
||||
|
||||
if (existing == null) {
|
||||
// Add that new address
|
||||
await _db.putAddress(nextAddress);
|
||||
} else {
|
||||
// we need to update the address
|
||||
await _db.updateAddress(existing, nextAddress);
|
||||
}
|
||||
// keep checking until address with no tx history is set as current
|
||||
await checkCurrentPaynymReceivingAddressForTransactions(sender);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,15 +329,13 @@ mixin PaynymWalletInterface {
|
|||
const maxCount = 2147483647;
|
||||
|
||||
for (int i = startIndex; i < maxCount; i++) {
|
||||
final key = await lookupKey(pCode.toString());
|
||||
final keys = await lookupKey(pCode.toString());
|
||||
final address = await _db
|
||||
.getAddresses(_walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymSend)
|
||||
.and()
|
||||
.otherDataEqualTo(key)
|
||||
.and()
|
||||
.otherDataIsNotNull()
|
||||
.anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e))
|
||||
.and()
|
||||
.derivationIndexEqualTo(i)
|
||||
.findFirst();
|
||||
|
@ -353,7 +384,7 @@ mixin PaynymWalletInterface {
|
|||
int additionalOutputs = 0,
|
||||
List<UTXO>? utxos,
|
||||
}) async {
|
||||
const amountToSend = DUST_LIMIT;
|
||||
final amountToSend = _dustLimitP2PKH;
|
||||
final List<UTXO> availableOutputs =
|
||||
utxos ?? await _db.getUTXOs(_walletId).findAll();
|
||||
final List<UTXO> spendableOutputs = [];
|
||||
|
@ -362,8 +393,8 @@ mixin PaynymWalletInterface {
|
|||
// Build list of spendable outputs and totaling their satoshi amount
|
||||
for (var i = 0; i < availableOutputs.length; i++) {
|
||||
if (availableOutputs[i].isBlocked == false &&
|
||||
availableOutputs[i].isConfirmed(
|
||||
await _getChainHeight(), MINIMUM_CONFIRMATIONS) ==
|
||||
availableOutputs[i]
|
||||
.isConfirmed(await _getChainHeight(), _minConfirms) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
|
@ -433,22 +464,24 @@ mixin PaynymWalletInterface {
|
|||
feeRatePerKB: selectedTxFeeRate,
|
||||
);
|
||||
|
||||
if (feeForNoChange < vSizeForNoChange * 1000) {
|
||||
feeForNoChange = vSizeForNoChange * 1000;
|
||||
}
|
||||
if (feeForWithChange < vSizeForWithChange * 1000) {
|
||||
feeForWithChange = vSizeForWithChange * 1000;
|
||||
if (_coin == Coin.dogecoin || _coin == Coin.dogecoinTestNet) {
|
||||
if (feeForNoChange < vSizeForNoChange * 1000) {
|
||||
feeForNoChange = vSizeForNoChange * 1000;
|
||||
}
|
||||
if (feeForWithChange < vSizeForWithChange * 1000) {
|
||||
feeForWithChange = vSizeForWithChange * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (satoshisBeingUsed - amountToSend > feeForNoChange + DUST_LIMIT) {
|
||||
if (satoshisBeingUsed - amountToSend > feeForNoChange + _dustLimitP2PKH) {
|
||||
// try to add change output due to "left over" amount being greater than
|
||||
// the estimated fee + the dust limit
|
||||
int changeAmount = satoshisBeingUsed - amountToSend - feeForWithChange;
|
||||
|
||||
// check estimates are correct and build notification tx
|
||||
if (changeAmount >= DUST_LIMIT &&
|
||||
if (changeAmount >= _dustLimitP2PKH &&
|
||||
satoshisBeingUsed - amountToSend - changeAmount == feeForWithChange) {
|
||||
final txn = await _createNotificationTx(
|
||||
var txn = await _createNotificationTx(
|
||||
targetPaymentCodeString: targetPaymentCodeString,
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
|
@ -457,6 +490,18 @@ mixin PaynymWalletInterface {
|
|||
|
||||
int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount;
|
||||
|
||||
// make sure minimum fee is accurate if that is being used
|
||||
if (txn.item2 - feeBeingPaid == 1) {
|
||||
changeAmount -= 1;
|
||||
feeBeingPaid += 1;
|
||||
txn = await _createNotificationTx(
|
||||
targetPaymentCodeString: targetPaymentCodeString,
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
change: changeAmount,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> transactionObject = {
|
||||
"hex": txn.item1,
|
||||
"recipientPaynym": targetPaymentCodeString,
|
||||
|
@ -569,10 +614,13 @@ mixin PaynymWalletInterface {
|
|||
txb.addInput(
|
||||
utxo.txid,
|
||||
txPointIndex,
|
||||
null,
|
||||
utxoSigningData[utxo.txid]["output"] as Uint8List,
|
||||
);
|
||||
|
||||
// todo: modify address once segwit support is in our bip47
|
||||
txb.addOutput(targetPaymentCode.notificationAddressP2PKH(), DUST_LIMIT);
|
||||
txb.addOutput(
|
||||
targetPaymentCode.notificationAddressP2PKH(), _dustLimitP2PKH);
|
||||
txb.addOutput(opReturnScript, 0);
|
||||
|
||||
// TODO: add possible change output and mark output as dangerous
|
||||
|
@ -586,15 +634,18 @@ mixin PaynymWalletInterface {
|
|||
txb.sign(
|
||||
vin: 0,
|
||||
keyPair: myKeyPair,
|
||||
witnessValue: utxo.value,
|
||||
witnessScript: utxoSigningData[utxo.txid]["redeemScript"] as Uint8List?,
|
||||
);
|
||||
|
||||
// sign rest of possible inputs
|
||||
for (var i = 1; i < utxosToUse.length - 1; i++) {
|
||||
for (var i = 1; i < utxosToUse.length; i++) {
|
||||
final txid = utxosToUse[i].txid;
|
||||
txb.sign(
|
||||
vin: i,
|
||||
keyPair: utxoSigningData[txid]["keyPair"] as btc_dart.ECPair,
|
||||
// witnessValue: utxosToUse[i].value,
|
||||
witnessValue: utxosToUse[i].value,
|
||||
witnessScript: utxoSigningData[utxo.txid]["redeemScript"] as Uint8List?,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -655,6 +706,13 @@ mixin PaynymWalletInterface {
|
|||
if (paymentCodeString == unBlindedPaymentCode.toString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tx.address.value?.otherData != null) {
|
||||
final code = await paymentCodeStringByKey(tx.address.value!.otherData!);
|
||||
if (code == paymentCodeString) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise return no
|
||||
|
@ -671,8 +729,13 @@ mixin PaynymWalletInterface {
|
|||
}
|
||||
|
||||
try {
|
||||
final blindedCode =
|
||||
transaction.outputs.elementAt(1).scriptPubKeyAsm!.split(" ")[1];
|
||||
final blindedCodeBytes =
|
||||
Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction);
|
||||
|
||||
// transaction does not contain a payment code
|
||||
if (blindedCodeBytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final designatedInput = transaction.inputs.first;
|
||||
|
||||
|
@ -693,7 +756,7 @@ mixin PaynymWalletInterface {
|
|||
|
||||
final mask = PaymentCode.getMask(S.ecdhSecret(), rev);
|
||||
|
||||
final unBlindedPayload = PaymentCode.blind(blindedCode.fromHex, mask);
|
||||
final unBlindedPayload = PaymentCode.blind(blindedCodeBytes, mask);
|
||||
|
||||
final unBlindedPaymentCode =
|
||||
PaymentCode.initFromPayload(unBlindedPayload);
|
||||
|
@ -718,33 +781,99 @@ mixin PaynymWalletInterface {
|
|||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.findAll();
|
||||
|
||||
List<PaymentCode> unBlindedList = [];
|
||||
List<PaymentCode> codes = [];
|
||||
|
||||
for (final tx in txns) {
|
||||
final unBlinded = await unBlindedPaymentCodeFromTransaction(
|
||||
transaction: tx,
|
||||
myNotificationAddress: myAddress,
|
||||
);
|
||||
if (unBlinded != null &&
|
||||
unBlindedList
|
||||
.where((e) => e.toString() == unBlinded.toString())
|
||||
.isEmpty) {
|
||||
unBlindedList.add(unBlinded);
|
||||
// tx is sent so we can check the address's otherData for the code String
|
||||
if (tx.type == TransactionType.outgoing &&
|
||||
tx.address.value?.otherData != null) {
|
||||
final codeString =
|
||||
await paymentCodeStringByKey(tx.address.value!.otherData!);
|
||||
if (codeString != null &&
|
||||
codes.where((e) => e.toString() == codeString).isEmpty) {
|
||||
codes.add(PaymentCode.fromPaymentCode(codeString, _network));
|
||||
}
|
||||
} else {
|
||||
// otherwise we need to un blind the code
|
||||
final unBlinded = await unBlindedPaymentCodeFromTransaction(
|
||||
transaction: tx,
|
||||
myNotificationAddress: myAddress,
|
||||
);
|
||||
if (unBlinded != null &&
|
||||
codes.where((e) => e.toString() == unBlinded.toString()).isEmpty) {
|
||||
codes.add(unBlinded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unBlindedList;
|
||||
return codes;
|
||||
}
|
||||
|
||||
Future<void> checkForNotificationTransactionsTo(
|
||||
Set<String> otherCodeStrings) async {
|
||||
final sentNotificationTransactions = await _db
|
||||
.getTransactions(_walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.and()
|
||||
.typeEqualTo(TransactionType.outgoing)
|
||||
.findAll();
|
||||
|
||||
final List<PaymentCode> codes = [];
|
||||
for (final codeString in otherCodeStrings) {
|
||||
codes.add(PaymentCode.fromPaymentCode(codeString, _network));
|
||||
}
|
||||
|
||||
for (final tx in sentNotificationTransactions) {
|
||||
if (tx.address.value != null && tx.address.value!.otherData == null) {
|
||||
final oldAddress =
|
||||
await _db.getAddress(_walletId, tx.address.value!.value);
|
||||
for (final code in codes) {
|
||||
final notificationAddress = code.notificationAddressP2PKH();
|
||||
if (notificationAddress == oldAddress!.value) {
|
||||
final address = Address(
|
||||
walletId: _walletId,
|
||||
value: notificationAddress,
|
||||
publicKey: [],
|
||||
derivationIndex: 0,
|
||||
type: oldAddress.type,
|
||||
subType: AddressSubType.paynymNotification,
|
||||
otherData: await storeCode(code.toString()),
|
||||
);
|
||||
await _db.updateAddress(oldAddress, address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreAllHistory({
|
||||
required int maxUnusedAddressGap,
|
||||
required int maxNumberOfIndexesToCheck,
|
||||
required Set<String> paymentCodeStrings,
|
||||
}) async {
|
||||
final codes = await getAllPaymentCodesFromNotificationTransactions();
|
||||
final List<PaymentCode> extraCodes = [];
|
||||
for (final codeString in paymentCodeStrings) {
|
||||
if (codes.where((e) => e.toString() == codeString).isEmpty) {
|
||||
final extraCode = PaymentCode.fromPaymentCode(codeString, _network);
|
||||
if (extraCode.isValid()) {
|
||||
extraCodes.add(extraCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codes.addAll(extraCodes);
|
||||
|
||||
final List<Future<void>> futures = [];
|
||||
for (final code in codes) {
|
||||
futures.add(restoreHistoryWith(
|
||||
code, maxUnusedAddressGap, maxNumberOfIndexesToCheck));
|
||||
futures.add(
|
||||
restoreHistoryWith(
|
||||
code,
|
||||
maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await Future.wait(futures);
|
||||
|
@ -1082,16 +1211,17 @@ mixin PaynymWalletInterface {
|
|||
}
|
||||
|
||||
/// look up a key that corresponds to a payment code string
|
||||
Future<String?> lookupKey(String paymentCodeString) async {
|
||||
Future<List<String>> lookupKey(String paymentCodeString) async {
|
||||
final keys =
|
||||
(await _secureStorage.keys).where((e) => e.startsWith(kPCodeKeyPrefix));
|
||||
final List<String> result = [];
|
||||
for (final key in keys) {
|
||||
final value = await _secureStorage.read(key: key);
|
||||
if (value == paymentCodeString) {
|
||||
return key;
|
||||
result.add(key);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// fetch a payment code string
|
||||
|
|
|
@ -194,6 +194,8 @@ class _SVG {
|
|||
String get exitDesktop => "assets/svg/exit-desktop.svg";
|
||||
String get keys => "assets/svg/keys.svg";
|
||||
String get arrowDown => "assets/svg/arrow-down.svg";
|
||||
String get robotHead => "assets/svg/robot-head.svg";
|
||||
String get whirlPool => "assets/svg/whirlpool.svg";
|
||||
|
||||
String get ellipse1 => "assets/svg/Ellipse-43.svg";
|
||||
String get ellipse2 => "assets/svg/Ellipse-42.svg";
|
||||
|
@ -262,6 +264,7 @@ class _PNG {
|
|||
const _PNG();
|
||||
|
||||
String get stack => "assets/images/stack.png";
|
||||
String get unclaimedPaynym => "assets/images/unclaimed.png";
|
||||
String get splash => "assets/images/splash.png";
|
||||
|
||||
String get monero => "assets/images/monero.png";
|
||||
|
|
37
lib/utilities/bip47_utils.dart
Normal file
37
lib/utilities/bip47_utils.dart
Normal file
|
@ -0,0 +1,37 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bip47/src/util.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
|
||||
abstract class Bip47Utils {
|
||||
/// looks at tx outputs and returns a blinded payment code if found
|
||||
static Uint8List? getBlindedPaymentCodeBytesFrom(Transaction transaction) {
|
||||
for (int i = 0; i < transaction.outputs.length; i++) {
|
||||
final bytes = getBlindedPaymentCodeBytesFromOutput(
|
||||
transaction.outputs.elementAt(i));
|
||||
if (bytes != null) {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Uint8List? getBlindedPaymentCodeBytesFromOutput(Output output) {
|
||||
Uint8List? blindedCodeBytes;
|
||||
|
||||
List<String>? scriptChunks = output.scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
|
||||
// https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||
if (bytes.length == 80 && bytes.first == 1) {
|
||||
blindedCodeBytes = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
return blindedCodeBytes;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ abstract class FeaturedPaynyms {
|
|||
// static const String samouraiWalletDevFund =
|
||||
// "PM8TJYkuSdYXJnwDBq8ChfinfXv3srxhQrx3eoEwbSw51wMjdo9JJ2DsycwT3gt3zHQ7cV1grvabMmmf1Btj6fY7tgkgSz9B8MZuR3kjYfgMLMURJCXN";
|
||||
static const String stackWallet =
|
||||
"PM8TJPdEeH3A77h4xJYQeXPWix2W5yAJrzVQ8ggET1n92utnc57FXCoH94Z2wUSJNfGwkX1kNDTCQLkHecVsjQHGkDE8MUyWE4xWJcc1EDDYCeSSBfLL";
|
||||
"PM8TJdQcNk27JpxGRtNR7Hnh8VkJk4Nf17BthLx89fM3iX3UL2YshyaiTAvKgTCVvpgsAgY1DbojkAaUd3Rcn48NEn4uUBuqkaSddgKL8TPAAEQXNuE6";
|
||||
|
||||
static Map<String, String> get featured => {
|
||||
"Stack Wallet": stackWallet,
|
||||
|
|
|
@ -13,31 +13,31 @@ class STextStyles {
|
|||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -932,6 +932,76 @@ class STextStyles {
|
|||
}
|
||||
}
|
||||
|
||||
static TextStyle w600_14(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_14(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_12(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
|
@ -967,6 +1037,41 @@ class STextStyles {
|
|||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_10(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle syncPercent(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
|
@ -1030,7 +1135,7 @@ class STextStyles {
|
|||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
color: _theme(context).bottomNavIconIcon,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class Util {
|
||||
|
@ -22,6 +23,14 @@ abstract class Util {
|
|||
return Platform.isLinux || Platform.isMacOS || Platform.isWindows;
|
||||
}
|
||||
|
||||
static Future<bool> get isIPad async {
|
||||
final deviceInfo = (await DeviceInfoPlugin().deviceInfo);
|
||||
if (deviceInfo is IosDeviceInfo) {
|
||||
return (deviceInfo).name?.toLowerCase().contains("ipad") == true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static MaterialColor createMaterialColor(Color color) {
|
||||
List<double> strengths = <double>[.05];
|
||||
final swatch = <int, Color>{};
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/providers/ui/color_theme_provider.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
|
||||
class BlueTextButton extends ConsumerStatefulWidget {
|
||||
const BlueTextButton({
|
||||
class _CustomTextButton extends StatefulWidget {
|
||||
const _CustomTextButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
required this.enabledColor,
|
||||
required this.disabledColor,
|
||||
this.onTap,
|
||||
this.enabled = true,
|
||||
this.textSize,
|
||||
|
@ -21,12 +21,14 @@ class BlueTextButton extends ConsumerStatefulWidget {
|
|||
final VoidCallback? onTap;
|
||||
final bool enabled;
|
||||
final double? textSize;
|
||||
final Color enabledColor;
|
||||
final Color disabledColor;
|
||||
|
||||
@override
|
||||
ConsumerState<BlueTextButton> createState() => _BlueTextButtonState();
|
||||
State<_CustomTextButton> createState() => _CustomTextButtonState();
|
||||
}
|
||||
|
||||
class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
||||
class _CustomTextButtonState extends State<_CustomTextButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
AnimationController? controller;
|
||||
Animation<dynamic>? animation;
|
||||
|
@ -37,18 +39,14 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
|||
@override
|
||||
void initState() {
|
||||
if (widget.enabled) {
|
||||
color = ref.read(colorThemeProvider.state).state.buttonTextBorderless;
|
||||
color = widget.enabledColor;
|
||||
controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
animation = ColorTween(
|
||||
begin: ref.read(colorThemeProvider.state).state.buttonTextBorderless,
|
||||
end: ref
|
||||
.read(colorThemeProvider.state)
|
||||
.state
|
||||
.buttonTextBorderless
|
||||
.withOpacity(0.4),
|
||||
begin: widget.enabledColor,
|
||||
end: widget.enabledColor.withOpacity(0.4),
|
||||
).animate(controller!);
|
||||
|
||||
animation!.addListener(() {
|
||||
|
@ -57,7 +55,7 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
|||
});
|
||||
});
|
||||
} else {
|
||||
color = ref.read(colorThemeProvider.state).state.textSubtitle1;
|
||||
color = widget.disabledColor;
|
||||
}
|
||||
|
||||
super.initState();
|
||||
|
@ -116,3 +114,32 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTextButton extends StatelessWidget {
|
||||
const CustomTextButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.onTap,
|
||||
this.enabled = true,
|
||||
this.textSize,
|
||||
}) : super(key: key);
|
||||
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final bool enabled;
|
||||
final double? textSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _CustomTextButton(
|
||||
key: UniqueKey(),
|
||||
text: text,
|
||||
enabledColor:
|
||||
Theme.of(context).extension<StackColors>()!.buttonTextBorderless,
|
||||
disabledColor: Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||
enabled: enabled,
|
||||
textSize: textSize,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -272,8 +272,8 @@ class _PaynymFollowToggleButtonState
|
|||
switch (widget.style) {
|
||||
case PaynymFollowToggleButtonStyle.primary:
|
||||
return PrimaryButton(
|
||||
width: isDesktop ? 120 : 84,
|
||||
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l,
|
||||
width: isDesktop ? 120 : 100,
|
||||
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.xl,
|
||||
label: isFollowing ? "Unfollow" : "Follow",
|
||||
onPressed: _onPressed,
|
||||
);
|
||||
|
@ -281,15 +281,15 @@ class _PaynymFollowToggleButtonState
|
|||
case PaynymFollowToggleButtonStyle.detailsPopup:
|
||||
return SecondaryButton(
|
||||
label: isFollowing ? "Unfollow" : "Follow",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: SvgPicture.asset(
|
||||
isFollowing ? Assets.svg.userMinus : Assets.svg.userPlus,
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.buttonTextSecondary,
|
||||
),
|
||||
iconSpacing: 4,
|
||||
onPressed: _onPressed,
|
||||
);
|
||||
|
||||
|
|
|
@ -78,6 +78,16 @@ class SecondaryButton extends StatelessWidget {
|
|||
.buttonTextSecondaryDisabled,
|
||||
);
|
||||
}
|
||||
if (buttonHeight == ButtonHeight.xl) {
|
||||
return STextStyles.button(context).copyWith(
|
||||
fontSize: 14,
|
||||
color: enabled
|
||||
? Theme.of(context).extension<StackColors>()!.buttonTextSecondary
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondaryDisabled,
|
||||
);
|
||||
}
|
||||
return STextStyles.button(context).copyWith(
|
||||
color: enabled
|
||||
? Theme.of(context).extension<StackColors>()!.buttonTextSecondary
|
||||
|
|
|
@ -261,7 +261,7 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
|||
const SizedBox(
|
||||
width: 66,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Connect",
|
||||
enabled: _status == "Disconnected",
|
||||
onTap: () async {
|
||||
|
@ -285,7 +285,7 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
|||
const SizedBox(
|
||||
width: 48,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Details",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:stackwallet/models/isar/models/isar_models.dart';
|
|||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -121,7 +120,8 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
|
|||
}
|
||||
}
|
||||
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
|
|
@ -14,7 +14,7 @@ import flutter_secure_storage_macos
|
|||
import isar_flutter_libs
|
||||
import package_info_plus_macos
|
||||
import path_provider_macos
|
||||
import share_plus_macos
|
||||
import share_plus
|
||||
import shared_preferences_macos
|
||||
import stack_wallet_backup
|
||||
import url_launcher_macos
|
||||
|
|
30
pubspec.lock
30
pubspec.lock
|
@ -1237,21 +1237,7 @@ packages:
|
|||
name: share_plus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.5.3"
|
||||
share_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
share_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "6.3.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1259,20 +1245,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
share_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
share_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -11,7 +11,7 @@ description: Stack Wallet
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.5.36+113
|
||||
version: 1.5.36+117
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
@ -126,7 +126,7 @@ dependencies:
|
|||
tuple: ^2.0.0
|
||||
flutter_riverpod: ^1.0.3
|
||||
qr_flutter: ^4.0.0
|
||||
share_plus: ^4.0.10
|
||||
share_plus: ^6.3.0
|
||||
emojis: ^0.9.9
|
||||
pointycastle: ^3.6.0
|
||||
package_info_plus: ^1.4.2
|
||||
|
@ -205,6 +205,7 @@ flutter:
|
|||
- assets/svg/circle-check.svg
|
||||
- assets/svg/clipboard.svg
|
||||
- assets/images/stack.png
|
||||
- assets/images/unclaimed.png
|
||||
- assets/images/monero.png
|
||||
- assets/images/wownero.png
|
||||
- assets/images/firo.png
|
||||
|
@ -309,6 +310,8 @@ flutter:
|
|||
- assets/svg/plus-circle.svg
|
||||
- assets/svg/circle-plus-filled.svg
|
||||
- assets/svg/configuration.svg
|
||||
- assets/svg/robot-head.svg
|
||||
- assets/svg/whirlpool.svg
|
||||
# coin icons
|
||||
- assets/svg/coin_icons/Bitcoin.svg
|
||||
- assets/svg/coin_icons/Litecoin.svg
|
||||
|
|
|
@ -80,6 +80,8 @@ void main() {
|
|||
|
||||
when(wallets.getManager("wallet-id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
//
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
|
@ -173,6 +175,7 @@ void main() {
|
|||
.thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00));
|
||||
|
||||
when(wallet.coin).thenAnswer((_) => Coin.firo);
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
|
||||
when(wallets.getManager("wallet-id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
@ -271,6 +274,8 @@ void main() {
|
|||
when(wallets.getManager("wallet-id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
|
@ -358,6 +363,8 @@ void main() {
|
|||
when(wallets.getManager("wallet id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
|
||||
mockingjay
|
||||
.when(() => navigator.pushNamed("/transactionDetails",
|
||||
arguments: Tuple3(tx, Coin.firo, "wallet id")))
|
||||
|
@ -395,6 +402,7 @@ void main() {
|
|||
verify(mockPrefs.currency).called(2);
|
||||
verify(mockLocaleService.locale).called(4);
|
||||
verify(wallet.coin.ticker).called(2);
|
||||
verify(wallet.storedChainHeight).called(2);
|
||||
|
||||
verifyNoMoreInteractions(wallet);
|
||||
verifyNoMoreInteractions(mockLocaleService);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <stack_wallet_backup/stack_wallet_backup_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <window_size/window_size_plugin.h>
|
||||
|
@ -26,6 +27,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
StackWalletBackupPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("StackWalletBackupPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
|
|
@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
flutter_secure_storage_windows
|
||||
isar_flutter_libs
|
||||
permission_handler_windows
|
||||
share_plus
|
||||
stack_wallet_backup
|
||||
url_launcher_windows
|
||||
window_size
|
||||
|
|
Loading…
Reference in a new issue