Merge remote-tracking branch 'cypherstack/staging' into tests/particl

This commit is contained in:
sneurlax 2022-11-29 11:02:20 -06:00
commit 17f080417e
354 changed files with 53955 additions and 20578 deletions

View file

@ -23,6 +23,7 @@ jobs:
run: |
cargo install cargo-ndk
rustup target add x86_64-unknown-linux-gnu
sudo apt update
sudo apt install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm
sudo apt install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev
sudo apt install -y libc6-dev-i386

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
assets/images/glasses.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/images/litecoin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

6
assets/svg/Button.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.44444 2.21973C2.09618 2.21973 1 3.31591 1 4.66417V15.6642C1 17.0124 2.09618 18.1086 3.44444 18.1086H10.1667L9.75799 19.3308H7.11111C6.43507 19.3308 5.88889 19.877 5.88889 20.5531C5.88889 21.2291 6.43507 21.7753 7.11111 21.7753H16.8889C17.5649 21.7753 18.1111 21.2291 18.1111 20.5531C18.1111 19.877 17.5649 19.3308 16.8889 19.3308H14.242L13.8333 18.1086H20.5556C21.9038 18.1086 23 17.0124 23 15.6642V4.66417C23 3.31591 21.9038 2.21973 20.5556 2.21973H3.44444ZM20.5556 4.66417V13.2197H3.44444V4.66417H20.5556Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 641 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.8242 1H4.44922C2.93027 1 1.69922 2.23105 1.69922 3.75V20.25C1.69922 21.7689 2.93027 23 4.44922 23H16.8242C18.3432 23 19.5742 21.7689 19.5742 20.25V3.75C19.5742 2.23105 18.341 1 16.8242 1ZM10.6367 6.5C12.1557 6.5 13.3867 7.73105 13.3867 9.25C13.3867 10.7689 12.1557 12 10.6367 12C9.1182 12 7.88672 10.7689 7.88672 9.25C7.88672 7.73105 9.11992 6.5 10.6367 6.5ZM14.7617 17.5H6.51172C6.13359 17.5 5.82422 17.1906 5.82422 16.8125C5.82422 14.9133 7.3625 13.375 9.26172 13.375H12.0117C13.9101 13.375 15.4492 14.9141 15.4492 16.8125C15.4492 17.1906 15.1398 17.5 14.7617 17.5ZM21.6367 3.75H20.9492V7.875H21.6367C22.0148 7.875 22.3242 7.56563 22.3242 7.1875V4.4375C22.3242 4.05766 22.0148 3.75 21.6367 3.75ZM21.6367 9.25H20.9492V13.375H21.6367C22.0148 13.375 22.3242 13.0656 22.3242 12.6875V9.9375C22.3242 9.55937 22.0148 9.25 21.6367 9.25ZM21.6367 14.75H20.9492V18.875H21.6367C22.0164 18.875 22.3242 18.5672 22.3242 18.1875V15.4375C22.3242 15.0594 22.0148 14.75 21.6367 14.75Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,4 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 4.16602V15.8327" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.3327 10L10.4993 15.8333L4.66602 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 346 B

View file

@ -0,0 +1,3 @@
<svg width="12" height="7" viewBox="0 0 12 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 6L6 1L1 6" stroke="#8E9192" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 208 B

View file

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6052_99642)">
<rect width="24" height="24" rx="12" fill="white"/>
<path d="M11.9976 0C9.62389 0 7.30353 0.703873 5.3299 2.02261C3.35627 3.34135 1.81802 5.21572 0.909655 7.4087C0.00129377 9.60167 -0.236375 12.0148 0.226704 14.3428C0.689782 16.6709 1.83281 18.8093 3.51124 20.4878C5.18968 22.1662 7.32813 23.3092 9.65618 23.7723C11.9842 24.2354 14.3973 23.9977 16.5903 23.0893C18.7833 22.181 20.6577 20.6427 21.9764 18.6691C23.2951 16.6955 23.999 14.3751 23.999 12.0015C23.999 10.4254 23.6886 8.86478 23.0854 7.4087C22.4823 5.95261 21.5983 4.62958 20.4839 3.51514C19.3694 2.40071 18.0464 1.51669 16.5903 0.913556C15.1342 0.310427 13.5736 0 11.9976 0V0ZM12.1921 12.3963L10.9437 16.6087H17.6209C17.674 16.6088 17.7263 16.6213 17.7738 16.6451C17.8212 16.669 17.8625 16.7035 17.8943 16.746C17.9261 16.7885 17.9476 16.8378 17.9571 16.8901C17.9666 16.9423 17.9638 16.9961 17.9489 17.0471L17.3683 19.0473C17.3406 19.1429 17.2826 19.2268 17.203 19.2865C17.1234 19.3462 17.0265 19.3784 16.927 19.3783H6.72841L8.45286 13.5546L6.54551 14.1352L6.96646 12.7737L8.87671 12.1931L11.2979 4.0121C11.3245 3.91623 11.3818 3.8317 11.4609 3.77142C11.5401 3.71114 11.6368 3.67842 11.7363 3.67824H14.32C14.3731 3.67805 14.4254 3.69017 14.4729 3.71364C14.5205 3.73712 14.5619 3.77131 14.594 3.81352C14.6261 3.85573 14.6479 3.90482 14.6578 3.95691C14.6677 4.009 14.6654 4.06267 14.651 4.11371L12.6188 11.0318L14.5262 10.4512L14.1168 11.836L12.1921 12.3963Z" fill="#315D9E"/>
</g>
<defs>
<clipPath id="clip0_6052_99642">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,11 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<g clip-path="url(#clip0_5813_29086)">
<path d="M13.5 30.5625C13.5 29.8365 14.0878 29.25 14.8125 29.25H17.0544C17.5605 28.0893 18.7172 27.2812 20.0625 27.2812C21.4078 27.2812 22.5275 28.0893 23.0689 29.25H33.1875C33.9135 29.25 34.5 29.8365 34.5 30.5625C34.5 31.2885 33.9135 31.875 33.1875 31.875H23.0689C22.5275 33.0357 21.4078 33.8438 20.0625 33.8438C18.7172 33.8438 17.5605 33.0357 17.0544 31.875H14.8125C14.0878 31.875 13.5 31.2885 13.5 30.5625ZM21.375 30.5625C21.375 29.8365 20.7885 29.25 20.0625 29.25C19.3365 29.25 18.75 29.8365 18.75 30.5625C18.75 31.2885 19.3365 31.875 20.0625 31.875C20.7885 31.875 21.375 31.2885 21.375 30.5625ZM27.9375 20.7188C29.2828 20.7188 30.4025 21.5268 30.9439 22.6875H33.1875C33.9135 22.6875 34.5 23.274 34.5 24C34.5 24.726 33.9135 25.3125 33.1875 25.3125H30.9439C30.4025 26.4732 29.2828 27.2812 27.9375 27.2812C26.5922 27.2812 25.4355 26.4732 24.9311 25.3125H14.8125C14.0878 25.3125 13.5 24.726 13.5 24C13.5 23.274 14.0878 22.6875 14.8125 22.6875H24.9311C25.4355 21.5268 26.5922 20.7188 27.9375 20.7188ZM29.25 24C29.25 23.274 28.6635 22.6875 27.9375 22.6875C27.2115 22.6875 26.625 23.274 26.625 24C26.625 24.726 27.2115 25.3125 27.9375 25.3125C28.6635 25.3125 29.25 24.726 29.25 24ZM33.1875 16.125C33.9135 16.125 34.5 16.7128 34.5 17.4375C34.5 18.1635 33.9135 18.75 33.1875 18.75H24.3814C23.84 19.9107 22.7203 20.7188 21.375 20.7188C20.0297 20.7188 18.873 19.9107 18.3686 18.75H14.8125C14.0878 18.75 13.5 18.1635 13.5 17.4375C13.5 16.7128 14.0878 16.125 14.8125 16.125H18.3686C18.873 14.9663 20.0297 14.1562 21.375 14.1562C22.7203 14.1562 23.84 14.9663 24.3814 16.125H33.1875ZM20.0625 17.4375C20.0625 18.1635 20.649 18.75 21.375 18.75C22.101 18.75 22.6875 18.1635 22.6875 17.4375C22.6875 16.7128 22.101 16.125 21.375 16.125C20.649 16.125 20.0625 16.7128 20.0625 17.4375Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_5813_29086">
<rect width="21" height="21" fill="white" transform="translate(13.5 13.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

24
assets/svg/dark-theme.svg Normal file
View file

@ -0,0 +1,24 @@
<svg width="200" height="162" viewBox="0 0 200 162" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5863_29353)">
<rect width="200" height="162" rx="8" fill="#2A2D34"/>
<rect x="10" y="10" width="180" height="20" rx="2" fill="#444953"/>
<rect x="16" y="16" width="106" height="8" rx="1" fill="#7E8692"/>
<rect x="10" y="40" width="180" height="20" rx="2" fill="#333942"/>
<rect x="16" y="46" width="106" height="8" rx="1" fill="#575C63"/>
<rect x="10" y="62" width="180" height="20" rx="2" fill="#333942"/>
<rect x="16" y="68" width="106" height="8" rx="1" fill="#575C63"/>
<rect x="10" y="84" width="180" height="20" rx="2" fill="#333942"/>
<rect x="16" y="90" width="106" height="8" rx="1" fill="#575C63"/>
<rect x="10" y="106" width="180" height="20" rx="2" fill="#333942"/>
<rect x="16" y="112" width="106" height="8" rx="1" fill="#575C63"/>
<rect x="10" y="128" width="180" height="20" rx="2" fill="#333942"/>
<rect x="16" y="134" width="106" height="8" rx="1" fill="#575C63"/>
<rect x="10" y="150" width="180" height="20" rx="2" fill="#333942"/>
<rect x="16" y="156" width="106" height="8" rx="1" fill="#575C63"/>
</g>
<defs>
<clipPath id="clip0_5863_29353">
<rect width="200" height="162" rx="8" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<path d="M23.659 10.0005C24.8164 10.0005 25.7475 10.9367 25.7475 12.1005V13.483C26.8701 13.623 27.9926 13.973 29.089 14.2792C30.2029 14.5855 30.8555 15.7405 30.5423 16.8605C30.2377 17.9805 29.089 18.6367 27.9752 18.3217C27.1485 18.0942 26.3218 17.8492 25.4777 17.683C24.2073 17.438 22.7279 17.5517 21.5358 18.0767C20.4654 18.5405 19.5865 19.6605 20.7961 20.4392C21.9448 21.183 23.3893 21.4805 24.6772 21.8567C26.1913 22.2855 28.1144 22.8367 29.5589 23.8255C31.4386 25.1205 32.3175 27.2205 31.8998 29.478C31.5082 31.6567 29.994 33.0917 28.184 33.8267C27.4357 34.133 26.609 34.2467 25.7475 34.4217V35.9005C25.7475 37.0642 24.8164 38.0005 23.659 38.0005C22.5017 38.0005 21.5706 37.0642 21.5706 35.9005L21.4923 34.2117C20.1696 33.8967 18.7947 33.4417 17.4372 32.9955C16.3407 32.628 15.749 31.438 16.1058 30.3442C16.4713 29.2417 17.5764 28.6467 18.7425 29.0055C20.0738 29.443 21.4313 29.968 22.8063 30.178C24.4509 30.423 25.7649 30.2742 26.6264 29.9242C27.7838 29.4605 28.332 28.078 27.2007 27.2905C26.0347 26.4942 24.5379 26.1792 23.2065 25.803C21.7446 25.383 19.9259 24.8667 18.551 23.983C16.6627 22.7667 15.7055 20.7367 16.1145 18.5055C16.4974 16.3792 18.142 14.9705 19.8737 14.218C20.4045 13.9905 20.9788 13.8067 21.4923 13.6667V12.1005C21.4923 10.9367 22.5017 10.0005 23.5807 10.0005H23.659Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.69844 6.70024C9.61406 3.78461 14.325 3.77055 17.2594 6.65336L15.3281 8.57993C15.0047 8.90336 14.9109 9.38618 15.0844 9.80805C15.2578 10.2299 15.6703 10.5018 16.125 10.5018H21.7266H22.125C22.7484 10.5018 23.25 10.0002 23.25 9.3768V3.3768C23.25 2.92211 22.9781 2.50961 22.5563 2.33618C22.1344 2.16274 21.6516 2.25649 21.3281 2.57993L19.3781 4.52993C15.2719 0.475238 8.65781 0.489301 4.575 4.5768C3.43125 5.72055 2.60625 7.06586 2.1 8.50492C1.82344 9.28774 2.23594 10.1409 3.01406 10.4174C3.79219 10.694 4.65 10.2815 4.92656 9.50336C5.2875 8.48149 5.87344 7.52055 6.69844 6.70024ZM0.75 14.6268V14.9831V15.0159V20.6268C0.75 21.0815 1.02187 21.494 1.44375 21.6674C1.86562 21.8409 2.34844 21.7471 2.67188 21.4237L4.62187 19.4737C8.72812 23.5284 15.3422 23.5143 19.425 19.4268C20.5688 18.2831 21.3984 16.9377 21.9047 15.5034C22.1812 14.7206 21.7687 13.8674 20.9906 13.5909C20.2125 13.3143 19.3547 13.7268 19.0781 14.5049C18.7172 15.5268 18.1313 16.4877 17.3063 17.3081C14.3906 20.2237 9.67969 20.2377 6.74531 17.3549L8.67188 15.4237C8.99531 15.1002 9.08906 14.6174 8.91562 14.1956C8.74219 13.7737 8.32969 13.5018 7.875 13.5018H2.26875H2.23594H1.875C1.25156 13.5018 0.75 14.0034 0.75 14.6268Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,3 @@
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.875 3.12012C7.63555 3.12012 8.25 2.50566 8.25 1.74512C8.25 0.98457 7.63555 0.370117 6.875 0.370117H4.125C1.84766 0.370117 0 2.21777 0 4.49512V15.4951C0 17.7725 1.84766 19.6201 4.125 19.6201H6.875C7.63555 19.6201 8.25 19.0057 8.25 18.2451C8.25 17.4846 7.63555 16.8701 6.875 16.8701H4.125C3.36445 16.8701 2.75 16.2557 2.75 15.4951V4.49512C2.75 3.73457 3.36445 3.12012 4.125 3.12012H6.875ZM21.6777 10.7428C21.884 10.5494 22 10.2787 22 9.99512C22 9.71152 21.884 9.44082 21.6777 9.24746L15.4902 3.40371C15.1895 3.12012 14.7512 3.04277 14.373 3.20605C13.9949 3.36934 13.75 3.74316 13.75 4.15137V7.24512H8.25C7.48945 7.24512 6.875 7.85957 6.875 8.62012V11.3701C6.875 12.1307 7.48945 12.7451 8.25 12.7451H13.75V15.8389C13.75 16.2514 13.9949 16.6209 14.373 16.7842C14.7512 16.9475 15.1895 16.8701 15.4902 16.5865L21.6777 10.7428Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 954 B

View file

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="8" fill="#E0E3E3"/>
<path d="M26 8H12.5C10.843 8 9.5 9.34297 9.5 11V29C9.5 30.657 10.843 32 12.5 32H26C27.657 32 29 30.657 29 29V11C29 9.34297 27.6547 8 26 8ZM19.25 14C20.907 14 22.25 15.343 22.25 17C22.25 18.657 20.907 20 19.25 20C17.5934 20 16.25 18.657 16.25 17C16.25 15.343 17.5953 14 19.25 14ZM23.75 26H14.75C14.3375 26 14 25.6625 14 25.25C14 23.1781 15.6781 21.5 17.75 21.5H20.75C22.8209 21.5 24.5 23.1791 24.5 25.25C24.5 25.6625 24.1625 26 23.75 26ZM31.25 11H30.5V15.5H31.25C31.6625 15.5 32 15.1625 32 14.75V11.75C32 11.3356 31.6625 11 31.25 11ZM31.25 17H30.5V21.5H31.25C31.6625 21.5 32 21.1625 32 20.75V17.75C32 17.3375 31.6625 17 31.25 17ZM31.25 23H30.5V27.5H31.25C31.6642 27.5 32 27.1642 32 26.75V23.75C32 23.3375 31.6625 23 31.25 23Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 899 B

View file

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="8" fill="#E0E3E3"/>
<path d="M30.6765 16.1586C30.8183 16.5281 30.698 16.9449 30.4058 17.2156L28.5452 18.9086C28.5925 19.2652 28.6183 19.6305 28.6183 19.9613C28.6183 20.3695 28.5925 20.7348 28.5452 21.0914L30.4058 22.7844C30.698 23.0551 30.8183 23.4676 30.6765 23.8414C30.4874 24.3527 30.2597 24.8469 30.0019 25.3152L29.7999 25.6633C29.5163 26.1359 29.1984 26.5828 28.8503 27.0082C28.5925 27.3133 28.1757 27.4207 27.7976 27.3004L25.4042 26.5355C24.8284 26.9781 24.1538 27.3477 23.5136 27.6313L22.9765 30.0848C22.8906 30.4715 22.5898 30.7465 22.1945 30.8496C21.6015 30.9484 20.9913 31 20.3296 31C19.7452 31 19.1351 30.9484 18.5421 30.8496C18.1468 30.7465 17.846 30.4715 17.7601 30.0848L17.223 27.6313C16.5441 27.3477 15.9081 26.9781 15.3323 26.5355L12.9407 27.3004C12.5609 27.4207 12.1419 27.3133 11.8875 27.0082C11.5391 26.5828 11.2211 26.1359 10.9375 25.6633L10.7364 25.3152C10.4756 24.8469 10.2487 24.3527 10.0584 23.8414C9.91914 23.4719 10.0364 23.0551 10.3312 22.7844L12.19 21.0914C12.1428 20.7348 12.1183 20.3695 12.1183 20C12.1183 19.6305 12.1428 19.2652 12.19 18.9086L10.3312 17.2156C10.0364 16.9449 9.91914 16.5324 10.0584 16.1586C10.2487 15.6473 10.476 15.1531 10.7364 14.6848L10.9371 14.3367C11.2207 13.8641 11.5391 13.4172 11.8875 12.9939C12.1419 12.6867 12.5609 12.5802 12.9407 12.7013L15.3323 13.4645C15.9081 13.0202 16.5441 12.6506 17.223 12.37L17.7601 9.91652C17.846 9.52637 18.1468 9.21656 18.5421 9.15082C19.1351 9.05161 19.7452 9 20.3296 9C20.9913 9 21.6015 9.05161 22.1945 9.15082C22.5898 9.21656 22.8906 9.52637 22.9765 9.91652L23.5136 12.37C24.1538 12.6506 24.8284 13.0202 25.4042 13.4645L27.7976 12.7013C28.1757 12.5802 28.5925 12.6867 28.8503 12.9939C29.1984 13.4172 29.5163 13.8641 29.7999 14.3367L30.0019 14.6848C30.2597 15.1531 30.4874 15.6473 30.6765 16.1586ZM20.3683 23.4375C22.2675 23.4375 23.8058 21.8992 23.8058 19.9613C23.8058 18.1008 22.2675 16.5238 20.3683 16.5238C18.4691 16.5238 16.9308 18.1008 16.9308 19.9613C16.9308 21.8992 18.4691 23.4375 20.3683 23.4375Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

23
assets/svg/keys.svg Normal file
View file

@ -0,0 +1,23 @@
<svg width="99" height="57" viewBox="0 0 99 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6055_8386)">
<path d="M90.1893 8.04883H18.9405C14.0708 8.04883 10.123 11.9965 10.123 16.8663V47.6738C10.123 52.5436 14.0708 56.4913 18.9405 56.4913H90.1893C95.0591 56.4913 99.0068 52.5436 99.0068 47.6738V16.8663C99.0068 11.9965 95.0591 8.04883 90.1893 8.04883Z" fill="#E1E2E3"/>
<path d="M91.9528 5.84445V44.9928C91.9528 47.7275 89.7366 49.9437 87.002 49.9437H5.85138C3.11082 49.9437 0.894531 47.7275 0.894531 44.9928V5.84445C0.894531 3.10984 3.11082 0.893555 5.85138 0.893555L88.2888 1.06037C90.4038 1.63232 91.9528 3.55667 91.9528 5.84445V5.84445Z" stroke="#222222" stroke-width="3" stroke-miterlimit="10"/>
<path d="M15.9121 18.4336V32.4045" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M9.86523 21.9248L21.9595 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M21.9595 21.9248L9.86523 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M36.252 18.4336V32.4045" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M30.2051 21.9248L42.2993 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M42.2993 21.9248L30.2051 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M56.5918 18.4336V32.4045" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M50.5449 21.9248L62.6392 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M62.6392 21.9248L50.5449 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M76.9316 18.4336V32.4045" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M70.8848 21.9248L82.979 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M82.979 21.9248L70.8848 28.9073" stroke="#222222" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_6055_8386">
<rect width="99" height="56.4914" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,11 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<g clip-path="url(#clip0_5784_28907)">
<path d="M29.602 20.0474C30.0832 20.0474 30.477 20.3954 30.477 20.9067V21.0786H33.102C33.5832 21.0786 33.977 21.4267 33.977 21.938C33.977 22.4106 33.5832 22.7974 33.102 22.7974H33.0145L32.9445 22.9907C32.5551 24.0048 31.9601 24.9931 31.2076 25.8009C31.247 25.8224 31.2863 25.8095 31.3257 25.8696L32.1526 26.3552C32.5682 26.6001 32.6995 27.1286 32.4501 27.5368C32.2051 27.945 31.667 28.0739 31.2513 27.829L30.4245 27.3435C30.232 27.2274 30.0001 27.1071 29.8513 26.9782C29.392 27.3005 28.8932 27.5798 28.3682 27.8118L28.2063 27.8806C27.7645 28.0739 27.2482 27.8763 27.0513 27.4423C26.8545 27.0083 27.0557 26.5013 27.4976 26.3079L27.6551 26.2392C27.9351 26.1146 28.2063 25.9384 28.4645 25.8181L27.9351 25.2938C27.5895 24.9587 27.5895 24.4173 27.9351 24.0821C28.2763 23.7427 28.8276 23.7427 29.1688 24.0821L29.8076 24.7052L29.8338 24.6923C30.3763 24.1681 30.8182 23.5149 31.1376 22.7587H26.452C25.9313 22.7587 25.577 22.4106 25.577 21.8993C25.577 21.4267 25.9313 21.0399 26.452 21.0399H28.727V20.8681C28.727 20.3954 29.0813 20.0087 29.602 20.0087V20.0474ZM17.002 23.0208L17.8332 24.8599H16.1313L17.002 23.0208ZM10.002 18.5005C10.002 16.9815 11.2554 15.7505 12.802 15.7505H35.202C36.7463 15.7505 38.002 16.9815 38.002 18.5005V29.5005C38.002 31.0173 36.7463 32.2505 35.202 32.2505H12.802C11.2554 32.2505 10.002 31.0173 10.002 29.5005V18.5005ZM24.002 29.5005H35.202V18.5005H24.002V29.5005ZM17.8026 20.5587C17.6626 20.2493 17.3476 20.0474 17.002 20.0474C16.6563 20.0474 16.3413 20.2493 16.2013 20.5587L13.4022 26.7462C13.2062 27.1415 13.4048 27.6872 13.8467 27.8806C14.2881 28.0739 14.8057 27.8763 15.0026 27.4423L15.392 26.5399H18.612L19.0013 27.4423C19.1982 27.8763 19.7145 28.0739 20.1563 27.8806C20.5982 27.6872 20.7995 27.1415 20.6026 26.7462L17.8026 20.5587Z" fill="#232323"/>
</g>
<defs>
<clipPath id="clip0_5784_28907">
<rect width="28" height="22" fill="white" transform="translate(10.002 13.0005)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

24
assets/svg/light-mode.svg Normal file
View file

@ -0,0 +1,24 @@
<svg width="200" height="162" viewBox="0 0 200 162" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5887_94222)">
<rect width="200" height="162" rx="8" fill="#E8EAEC"/>
<rect x="10" y="10" width="180" height="20" rx="2" fill="#DBDDE1"/>
<rect x="16" y="16" width="106" height="8" rx="1" fill="#C4C8CC"/>
<rect x="10" y="40" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="46" width="106" height="8" rx="1" fill="#C4C8CC"/>
<rect x="10" y="62" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="68" width="106" height="8" rx="1" fill="#C4C8CC"/>
<rect x="10" y="84" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="90" width="106" height="8" rx="1" fill="#C4C8CC"/>
<rect x="10" y="106" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="112" width="106" height="8" rx="1" fill="#C4C8CC"/>
<rect x="10" y="128" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="134" width="106" height="8" rx="1" fill="#C4C8CC"/>
<rect x="10" y="150" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="156" width="106" height="8" rx="1" fill="#C4C8CC"/>
</g>
<defs>
<clipPath id="clip0_5887_94222">
<rect width="200" height="162" rx="8" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,11 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<g clip-path="url(#clip0_5764_28774)">
<path d="M24 10.9995C28.2589 10.9995 31.7143 14.254 31.7143 18.2687V20.6918H32.5714C34.4625 20.6918 36 22.1406 36 23.9226V33.6149C36 35.3969 34.4625 36.8457 32.5714 36.8457H15.4286C13.5348 36.8457 12 35.3969 12 33.6149V23.9226C12 22.1406 13.5348 20.6918 15.4286 20.6918H16.2857V18.2687C16.2857 14.254 19.7411 10.9995 24 10.9995ZM24 14.2303C21.6321 14.2303 19.7143 16.0385 19.7143 18.2687V20.6918H28.2857V18.2687C28.2857 16.0385 26.3679 14.2303 24 14.2303ZM25.7143 27.1534C25.7143 26.2598 24.9482 25.538 24 25.538C23.0518 25.538 22.2857 26.2598 22.2857 27.1534V30.3841C22.2857 31.2776 23.0518 31.9995 24 31.9995C24.9482 31.9995 25.7143 31.2776 25.7143 30.3841V27.1534Z" fill="#232323"/>
</g>
<defs>
<clipPath id="clip0_5764_28774">
<rect width="24" height="25.8462" fill="white" transform="translate(12 10.9995)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

3
assets/svg/lock-open.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1.75C5.79141 1.75 4.8125 2.72945 4.8125 3.9375V5.25H11.375C12.3402 5.25 13.125 6.03477 13.125 7V12.25C13.125 13.2152 12.3402 14 11.375 14H2.625C1.6584 14 0.875 13.2152 0.875 12.25V7C0.875 6.03477 1.6584 5.25 2.625 5.25H3.0625V3.9375C3.0625 1.76285 4.82617 0 7 0C8.57227 0 9.92578 0.921211 10.5574 2.24957C10.7652 2.68598 10.5793 3.20742 10.1199 3.41523C9.68242 3.62305 9.18476 3.43711 8.97695 2.99961C8.62422 2.25941 7.87227 1.75 7 1.75ZM7.875 10.5C8.35898 10.5 8.75 10.109 8.75 9.625C8.75 9.14102 8.35898 8.75 7.875 8.75H6.125C5.64102 8.75 5.25 9.14102 5.25 9.625C5.25 10.109 5.64102 10.5 6.125 10.5H7.875Z" fill="#0056D2"/>
</svg>

After

Width:  |  Height:  |  Size: 741 B

View file

@ -0,0 +1,10 @@
<svg width="24" height="19" viewBox="0 0 24 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6129_18406)">
<path d="M14.9173 0C15.8835 0 16.6673 0.769499 16.6673 1.71875V5.15625C16.6673 6.10514 15.8835 6.875 14.9173 6.875H13.1673V8.02083H22.5007C23.146 8.02083 23.6673 8.53288 23.6673 9.16667C23.6673 9.80046 23.146 10.3125 22.5007 10.3125H19.0007V11.4583H20.7507C21.7168 11.4583 22.5007 12.2282 22.5007 13.1771V16.6146C22.5007 17.5635 21.7168 18.3333 20.7507 18.3333H14.9173C13.9512 18.3333 13.1673 17.5635 13.1673 16.6146V13.1771C13.1673 12.2282 13.9512 11.4583 14.9173 11.4583H16.6673V10.3125H7.33398V11.4583H9.08398C10.0501 11.4583 10.834 12.2282 10.834 13.1771V16.6146C10.834 17.5635 10.0501 18.3333 9.08398 18.3333H3.25065C2.28414 18.3333 1.50065 17.5635 1.50065 16.6146V13.1771C1.50065 12.2282 2.28414 11.4583 3.25065 11.4583H5.00065V10.3125H1.50065C0.856432 10.3125 0.333984 9.80046 0.333984 9.16667C0.333984 8.53288 0.856432 8.02083 1.50065 8.02083H10.834V6.875H9.08398C8.11784 6.875 7.33398 6.10514 7.33398 5.15625V1.71875C7.33398 0.769499 8.11784 0 9.08398 0H14.9173ZM9.66732 2.29167V4.58333H14.334V2.29167H9.66732ZM8.50065 16.0417V13.75H3.83398V16.0417H8.50065ZM15.5007 13.75V16.0417H20.1673V13.75H15.5007Z" fill="#232323"/>
</g>
<defs>
<clipPath id="clip0_6129_18406">
<rect width="23.3333" height="18.3333" fill="white" transform="translate(0.333984)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<path d="M34.5 25.5H13.5C12.6741 25.5 12 26.1741 12 27V33C12 33.8259 12.6741 34.5 13.5 34.5H34.5C35.3259 34.5 36 33.8259 36 33V27C36 26.175 35.325 25.5 34.5 25.5ZM28.5 31.125C27.8789 31.125 27.375 30.6211 27.375 30C27.375 29.3789 27.8789 28.875 28.5 28.875C29.1211 28.875 29.625 29.3789 29.625 30C29.625 30.6211 29.1234 31.125 28.5 31.125ZM31.5 31.125C30.8789 31.125 30.375 30.6211 30.375 30C30.375 29.3789 30.8789 28.875 31.5 28.875C32.1211 28.875 32.625 29.3789 32.625 30C32.625 30.6211 32.1234 31.125 31.5 31.125ZM34.5 13.5H13.5C12.6741 13.5 12 14.1741 12 15V21C12 21.8259 12.6741 22.5 13.5 22.5H34.5C35.3259 22.5 36 21.8259 36 21V15C36 14.1741 35.325 13.5 34.5 13.5ZM28.5 19.125C27.8789 19.125 27.375 18.6211 27.375 18C27.375 17.3789 27.8813 16.875 28.5 16.875C29.1187 16.875 29.625 17.3812 29.625 18C29.625 18.6188 29.1234 19.125 28.5 19.125ZM31.5 19.125C30.8789 19.125 30.375 18.6211 30.375 18C30.375 17.3789 30.8813 16.875 31.5 16.875C32.1187 16.875 32.625 17.3812 32.625 18C32.625 18.6188 32.1234 19.125 31.5 19.125Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,28 @@
<svg width="200" height="162" viewBox="0 0 200 162" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_518_22068)">
<rect width="200" height="162" rx="8" fill="url(#paint0_linear_518_22068)"/>
<rect x="10" y="10" width="180" height="20" rx="2" fill="#C2DAE2"/>
<rect x="16" y="16" width="106" height="8" rx="1" fill="#227386"/>
<rect x="10" y="40" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="46" width="106" height="8" rx="1" fill="#BDD5DB"/>
<rect x="10" y="62" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="68" width="106" height="8" rx="1" fill="#BDD5DB"/>
<rect x="10" y="84" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="90" width="106" height="8" rx="1" fill="#BDD5DB"/>
<rect x="10" y="106" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="112" width="106" height="8" rx="1" fill="#BDD5DB"/>
<rect x="10" y="128" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="134" width="106" height="8" rx="1" fill="#BDD5DB"/>
<rect x="10" y="150" width="180" height="20" rx="2" fill="#FEFEFE"/>
<rect x="16" y="156" width="106" height="8" rx="1" fill="#BDD5DB"/>
</g>
<defs>
<linearGradient id="paint0_linear_518_22068" x1="100" y1="0" x2="100" y2="162" gradientUnits="userSpaceOnUse">
<stop stop-color="#F3F7FA"/>
<stop offset="1" stop-color="#E8F2F9"/>
</linearGradient>
<clipPath id="clip0_518_22068">
<rect width="200" height="162" rx="8" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 17.5C12.5 17.9193 12.2383 18.3672 11.7695 18.6797C11.3008 18.9922 10.6289 19.1667 10 19.1667C9.33594 19.1667 8.69922 18.9922 8.23047 18.6797C7.76172 18.3672 7.5 17.9193 7.5 17.5H12.5Z" fill="#227386"/>
<path d="M11.1903 1.98716V2.67947C13.9059 3.2142 15.9519 5.54245 15.9519 8.33331V9.0112C15.9519 10.7095 16.5955 12.3429 17.7561 13.6122L18.0314 13.9114C18.3439 14.254 18.422 14.7372 18.2286 15.1518C18.0351 15.5665 17.611 15.8333 17.1423 15.8333H2.85739C2.38867 15.8333 1.96351 15.5665 1.77148 15.1518C1.57945 14.7372 1.65626 14.254 1.96771 13.9114L2.24359 13.6122C3.40573 12.3429 4.0478 10.7095 4.0478 9.0112V8.33331C4.0478 5.54245 6.06034 3.2142 8.80945 2.67947V1.98716C8.80945 1.35002 9.34141 0.833313 9.99986 0.833313C10.6583 0.833313 11.1903 1.35002 11.1903 1.98716Z" fill="#227386"/>
<ellipse cx="17.0833" cy="2.91665" rx="2.08333" ry="2.08333" fill="#D34E50"/>
</svg>

After

Width:  |  Height:  |  Size: 987 B

View file

@ -0,0 +1,11 @@
<svg width="360" height="480" viewBox="0 0 360 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M48 174.503C45.4098 171.437 39.6642 169.267 34.578 176.625C29.4917 183.983 22.3192 180.197 19.7431 176.625C16.682 172.38 8.86422 166.438 2.08257 176.625C-0.507645 179.926 -7.2422 184.549 -13.4587 176.625C-19.6752 168.702 -26.4098 171.909 -29 174.503" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M48 188.503C45.4098 185.437 39.6642 183.267 34.578 190.625C29.4917 197.983 22.3192 194.197 19.7431 190.625C16.682 186.38 8.86422 180.438 2.08257 190.625C-0.507645 193.926 -7.2422 198.549 -13.4587 190.625C-19.6752 182.702 -26.4098 185.909 -29 188.503" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M48 202.503C45.4098 199.437 39.6642 197.267 34.578 204.625C29.4917 211.983 22.3192 208.197 19.7431 204.625C16.682 200.38 8.86422 194.438 2.08257 204.625C-0.507645 207.926 -7.2422 212.549 -13.4587 204.625C-19.6752 196.702 -26.4098 199.909 -29 202.503" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M389 444.503C386.41 441.437 380.664 439.267 375.578 446.625C370.492 453.983 363.319 450.197 360.743 446.625C357.682 442.38 349.864 436.438 343.083 446.625C340.492 449.926 333.758 454.549 327.541 446.625C321.325 438.702 314.59 441.909 312 444.503" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M389 458.503C386.41 455.437 380.664 453.267 375.578 460.625C370.492 467.983 363.319 464.197 360.743 460.625C357.682 456.38 349.864 450.438 343.083 460.625C340.492 463.926 333.758 468.549 327.541 460.625C321.325 452.702 314.59 455.909 312 458.503" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M389 472.503C386.41 469.437 380.664 467.267 375.578 474.625C370.492 481.983 363.319 478.197 360.743 474.625C357.682 470.38 349.864 464.438 343.083 474.625C340.492 477.926 333.758 482.549 327.541 474.625C321.325 466.702 314.59 469.909 312 472.503" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M389 4.5028C386.41 1.4371 380.664 -0.732664 375.578 6.62502C370.492 13.9827 363.319 10.1971 360.743 6.62502C357.682 2.38025 349.864 -3.56244 343.083 6.62502C340.492 9.92648 333.758 14.5485 327.541 6.62502C321.325 -1.29849 314.59 1.90875 312 4.5028" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M389 18.5028C386.41 15.4371 380.664 13.2673 375.578 20.625C370.492 27.9827 363.319 24.1971 360.743 20.625C357.682 16.3802 349.864 10.4376 343.083 20.625C340.492 23.9265 333.758 28.5485 327.541 20.625C321.325 12.7015 314.59 15.9087 312 18.5028" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
<path d="M389 32.5028C386.41 29.4371 380.664 27.2673 375.578 34.625C370.492 41.9827 363.319 38.1971 360.743 34.625C357.682 30.3802 349.864 24.4376 343.083 34.625C340.492 37.9265 333.758 42.5485 327.541 34.625C321.325 26.7015 314.59 29.9087 312 32.5028" stroke="#D3EAF3" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +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_519_18707)">
<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="#227386"/>
<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="#227386"/>
<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="#227386"/>
</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="#227386"/>
<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="#227386"/>
<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="#227386"/>
<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="#227386"/>
</g>
<defs>
<clipPath id="clip0_519_18707">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.5 6.5L20.4343 7.33045C20.8552 6.85685 20.8552 6.14315 20.4343 5.66955L19.5 6.5ZM16.4343 1.16955C15.9756 0.653567 15.1855 0.607091 14.6695 1.06574C14.1536 1.52439 14.1071 2.31448 14.5657 2.83045L16.4343 1.16955ZM14.5657 10.1695C14.1071 10.6855 14.1536 11.4756 14.6695 11.9343C15.1855 12.3929 15.9756 12.3464 16.4343 11.8305L14.5657 10.1695ZM0.75 10.5C0.75 11.1904 1.30964 11.75 2 11.75C2.69036 11.75 3.25 11.1904 3.25 10.5H0.75ZM6 7.75H19.5V5.25H6V7.75ZM14.5657 2.83045L18.5657 7.33045L20.4343 5.66955L16.4343 1.16955L14.5657 2.83045ZM16.4343 11.8305L20.4343 7.33045L18.5657 5.66955L14.5657 10.1695L16.4343 11.8305ZM3.25 10.5C3.25 8.98122 4.48122 7.75 6 7.75V5.25C3.10051 5.25 0.75 7.60051 0.75 10.5H3.25Z" fill="#227386"/>
<path opacity="0.4" d="M4.5 18L3.56574 17.1695C3.14475 17.6432 3.14475 18.3568 3.56574 18.8305L4.5 18ZM7.56574 23.3305C8.02439 23.8464 8.81448 23.8929 9.33045 23.4343C9.84643 22.9756 9.89291 22.1855 9.43426 21.6695L7.56574 23.3305ZM9.43426 14.3305C9.89291 13.8145 9.84643 13.0244 9.33046 12.5657C8.81448 12.1071 8.02439 12.1536 7.56574 12.6695L9.43426 14.3305ZM23.25 14C23.25 13.3096 22.6904 12.75 22 12.75C21.3096 12.75 20.75 13.3096 20.75 14L23.25 14ZM18 16.75L4.5 16.75L4.5 19.25L18 19.25L18 16.75ZM9.43426 21.6695L5.43426 17.1695L3.56574 18.8305L7.56574 23.3305L9.43426 21.6695ZM7.56574 12.6695L3.56574 17.1695L5.43426 18.8305L9.43426 14.3305L7.56574 12.6695ZM20.75 14C20.75 15.5188 19.5188 16.75 18 16.75L18 19.25C20.8995 19.25 23.25 16.8995 23.25 14L20.75 14Z" fill="#227386"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,5 @@
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M41.3715 9.57675C37.2965 7.22564 32.2041 10.1695 32.2041 14.8717C32.2041 19.5739 34.4762 23.6489 38.2004 26.163L53.9717 35.3057L54.0112 35.2908L69.9948 26.0543L41.3715 9.57675Z" fill="#B3B3B3"/>
<path d="M38.2014 26.163C34.4771 23.6489 32.205 19.4159 32.205 14.8717C32.205 12.6342 33.3757 10.7671 35.0402 9.7101C34.9612 9.75455 35.1192 9.66564 35.0402 9.7101L10.0917 23.7279L6.08593 26.1481L3.35449 27.7188C5.07337 26.8446 7.22692 26.7754 9.14831 27.8917L16.0189 31.8037L22.0399 35.2859L38.0236 44.5076L53.9677 35.2958L38.1964 26.1531L38.2014 26.163Z" fill="#666666"/>
<path d="M70 44.5187L38.0278 62.9917L31.992 59.5095L31.9673 59.4848L6.06054 44.5187C4.28733 43.3629 2.84505 41.7872 1.82755 40.014C0.642111 37.9691 0 35.618 0 33.1829C0 30.9899 1.10147 29.1771 2.70181 28.1004C2.91914 27.967 3.13153 27.8435 3.35874 27.725C5.07762 26.8507 7.23116 26.7816 9.15256 27.8979L15.9836 31.8394L22.0047 35.3068L22.0442 35.292L38.0278 44.5137L53.9719 35.3019L70 44.5137V44.5187Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.4" d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#0056D2"/>
<path d="M5.30071 12.4C4.91018 12.7905 4.91018 13.4236 5.30071 13.8142C5.69123 14.2047 6.32439 14.2047 6.71492 13.8142L5.30071 12.4ZM13.0789 6.03599L14.0787 6.05567C14.0839 5.78863 13.9821 5.53058 13.796 5.33904C13.6098 5.1475 13.3548 5.03839 13.0877 5.03603L13.0789 6.03599ZM9.00968 5.00004C8.45741 4.99516 8.00576 5.43891 8.00089 5.99117C7.99601 6.54344 8.43976 6.99509 8.99202 6.99996L9.00968 5.00004ZM12.001 9.98032C11.9902 10.5325 12.429 10.9889 12.9812 10.9998C13.5333 11.0107 13.9898 10.5719 14.0007 10.0197L12.001 9.98032ZM18.6429 11.6C19.0334 11.2095 19.0334 10.5764 18.6429 10.1858C18.2524 9.79531 17.6192 9.79531 17.2287 10.1858L18.6429 11.6ZM10.8647 17.964L9.8653 17.9297C9.85604 18.1992 9.95602 18.461 10.1426 18.6557C10.3291 18.8505 10.5864 18.9616 10.856 18.964L10.8647 17.964ZM14.9922 19C15.5444 19.0048 15.996 18.561 16.0008 18.0087C16.0056 17.4564 15.5618 17.0048 15.0096 17L14.9922 19ZM12.0003 14.0343C12.0192 13.4824 11.5871 13.0195 11.0352 13.0006C10.4832 12.9816 10.0204 13.4137 10.0014 13.9657L12.0003 14.0343ZM6.71492 13.8142L13.786 6.7431L12.3718 5.32889L5.30071 12.4L6.71492 13.8142ZM8.99202 6.99996L13.0701 7.03595L13.0877 5.03603L9.00968 5.00004L8.99202 6.99996ZM12.0791 6.01631L12.001 9.98032L14.0007 10.0197L14.0787 6.05567L12.0791 6.01631ZM17.2287 10.1858L10.1576 17.2569L11.5718 18.6711L18.6429 11.6L17.2287 10.1858ZM15.0096 17L10.8734 16.964L10.856 18.964L14.9922 19L15.0096 17ZM11.8641 17.9983L12.0003 14.0343L10.0014 13.9657L9.8653 17.9297L11.8641 17.9983Z" fill="#0056D2"/>
<circle cx="20.5" cy="20.5" r="3.5" fill="#C00205"/>
<path d="M19.4395 19.4395L20.5001 20.5001L21.5608 21.5608" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 21.5605L20.5607 20.4999L21.6213 19.4392" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.4" d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#0056D2"/>
<circle cx="20.5" cy="20.5" r="3.5" fill="#F4C517"/>
<path d="M20.5 19V20.5H21.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.30071 12.4C4.91018 12.7905 4.91018 13.4236 5.30071 13.8142C5.69123 14.2047 6.32439 14.2047 6.71492 13.8142L5.30071 12.4ZM13.0789 6.03599L14.0787 6.05567C14.0839 5.78863 13.9821 5.53058 13.796 5.33904C13.6098 5.1475 13.3548 5.03839 13.0877 5.03603L13.0789 6.03599ZM9.00968 5.00004C8.45741 4.99516 8.00576 5.43891 8.00089 5.99117C7.99601 6.54344 8.43976 6.99509 8.99202 6.99996L9.00968 5.00004ZM12.001 9.98032C11.9902 10.5325 12.429 10.9889 12.9812 10.9998C13.5333 11.0107 13.9898 10.5719 14.0007 10.0197L12.001 9.98032ZM18.6429 11.6C19.0334 11.2095 19.0334 10.5764 18.6429 10.1858C18.2524 9.79531 17.6192 9.79531 17.2287 10.1858L18.6429 11.6ZM10.8647 17.964L9.8653 17.9297C9.85605 18.1992 9.95602 18.461 10.1426 18.6557C10.3291 18.8505 10.5864 18.9616 10.856 18.964L10.8647 17.964ZM14.9922 19C15.5444 19.0048 15.996 18.561 16.0008 18.0087C16.0056 17.4564 15.5618 17.0048 15.0096 17L14.9922 19ZM12.0003 14.0343C12.0192 13.4824 11.5871 13.0195 11.0352 13.0006C10.4832 12.9816 10.0204 13.4137 10.0014 13.9657L12.0003 14.0343ZM6.71492 13.8142L13.786 6.7431L12.3718 5.32889L5.30071 12.4L6.71492 13.8142ZM8.99202 6.99996L13.0701 7.03595L13.0877 5.03603L9.00968 5.00004L8.99202 6.99996ZM12.0791 6.01631L12.001 9.98032L14.0007 10.0197L14.0787 6.05567L12.0791 6.01631ZM17.2287 10.1858L10.1576 17.2569L11.5718 18.6711L18.6429 11.6L17.2287 10.1858ZM15.0096 17L10.8734 16.964L10.856 18.964L14.9922 19L15.0096 17ZM11.8641 17.9983L12.0003 14.0343L10.0014 13.9657L9.8653 17.9297L11.8641 17.9983Z" fill="#0056D2"/>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle opacity="0.4" cx="12" cy="12" r="12" fill="#0056D2"/>
<path d="M5.30071 12.4C4.91018 12.7905 4.91018 13.4236 5.30071 13.8142C5.69123 14.2047 6.32439 14.2047 6.71492 13.8142L5.30071 12.4ZM13.0789 6.03599L14.0787 6.05567C14.0839 5.78863 13.9821 5.53058 13.796 5.33904C13.6098 5.1475 13.3548 5.03839 13.0877 5.03603L13.0789 6.03599ZM9.00968 5.00004C8.45741 4.99516 8.00576 5.43891 8.00089 5.99117C7.99601 6.54344 8.43976 6.99509 8.99202 6.99996L9.00968 5.00004ZM12.001 9.98032C11.9902 10.5325 12.429 10.9889 12.9812 10.9998C13.5333 11.0107 13.9898 10.5719 14.0007 10.0197L12.001 9.98032ZM18.6429 11.6C19.0334 11.2095 19.0334 10.5764 18.6429 10.1858C18.2524 9.79531 17.6192 9.79531 17.2287 10.1858L18.6429 11.6ZM10.8647 17.964L9.8653 17.9297C9.85604 18.1992 9.95602 18.461 10.1426 18.6557C10.3291 18.8505 10.5864 18.9616 10.856 18.964L10.8647 17.964ZM14.9922 19C15.5444 19.0048 15.996 18.561 16.0008 18.0087C16.0056 17.4564 15.5618 17.0048 15.0096 17L14.9922 19ZM12.0003 14.0343C12.0192 13.4824 11.5871 13.0195 11.0352 13.0006C10.4832 12.9816 10.0204 13.4137 10.0014 13.9657L12.0003 14.0343ZM6.71492 13.8142L13.786 6.7431L12.3718 5.32889L5.30071 12.4L6.71492 13.8142ZM8.99202 6.99996L13.0701 7.03595L13.0877 5.03603L9.00968 5.00004L8.99202 6.99996ZM12.0791 6.01631L12.001 9.98032L14.0007 10.0197L14.0787 6.05567L12.0791 6.01631ZM17.2287 10.1858L10.1576 17.2569L11.5718 18.6711L18.6429 11.6L17.2287 10.1858ZM15.0096 17L10.8734 16.964L10.856 18.964L14.9922 19L15.0096 17ZM11.8641 17.9983L12.0003 14.0343L10.0014 13.9657L9.8653 17.9297L11.8641 17.9983Z" fill="#0056D2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#00A578"/>
<circle cx="20.5" cy="20.5" r="3.5" fill="#C00205"/>
<path d="M16 8L8 16M8 16H14M8 16V10" stroke="#00A578" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.4395 19.4395L20.5001 20.5001L21.5608 21.5608" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 21.5605L20.5607 20.4999L21.6213 19.4392" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 882 B

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.4" d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#00A578"/>
<path d="M16 8L8 16M8 16H14M8 16V10" stroke="#00A578" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5 24C22.433 24 24 22.433 24 20.5C24 18.567 22.433 17 20.5 17C18.567 17 17 18.567 17 20.5C17 22.433 18.567 24 20.5 24ZM21 19C21 18.7239 20.7761 18.5 20.5 18.5C20.2239 18.5 20 18.7239 20 19V20.5C20 20.7761 20.2239 21 20.5 21H21.5C21.7761 21 22 20.7761 22 20.5C22 20.2239 21.7761 20 21.5 20H21V19Z" fill="#F4C517"/>
</svg>

After

Width:  |  Height:  |  Size: 912 B

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle opacity="0.4" cx="12" cy="12" r="12" fill="#00A578"/>
<path d="M16 8L8 16M8 16H14M8 16V10" stroke="#00A578" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View file

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.4" d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#FE805C"/>
<path d="M8 16L16 8M16 8L10 8M16 8L16 14" stroke="#FE805C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="20.5" cy="20.5" r="3.5" fill="#C00205"/>
<path d="M19.4395 19.4395L20.5001 20.5001L21.5608 21.5608" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 21.5605L20.5607 20.4999L21.6213 19.4392" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 847 B

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.4" d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#FE805C"/>
<path d="M8 16L16 8M16 8L10 8M16 8L16 14" stroke="#FE805C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="20.5" cy="20.5" r="3.5" fill="#F4C517"/>
<path d="M20.5 19V20.5H21.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 697 B

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle opacity="0.4" cx="12" cy="12" r="12" fill="#FE805C"/>
<path d="M8 16L16 8M16 8L10 8M16 8L16 14" stroke="#FE805C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 292 B

View file

@ -0,0 +1,12 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5370_82626)">
<path d="M9.99935 18.3337C14.6017 18.3337 18.3327 14.6027 18.3327 10.0003C18.3327 5.39795 14.6017 1.66699 9.99935 1.66699C5.39698 1.66699 1.66602 5.39795 1.66602 10.0003C1.66602 14.6027 5.39698 18.3337 9.99935 18.3337Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 6.66699V13.3337" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.66602 10H13.3327" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_5370_82626">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 780 B

View file

@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<path d="M32.25 17.5031V15.75C32.25 14.9217 32.9203 14.25 33.75 14.25C34.5797 14.25 35.25 14.9217 35.25 15.75V21C35.25 21.8297 34.5797 22.5 33.75 22.5H32.5219C32.4984 22.5 32.475 22.5 32.4516 22.5H28.5C27.6703 22.5 27 21.8297 27 21C27 20.1703 27.6703 19.5 28.5 19.5H30C28.6313 17.6766 26.4516 16.5 23.9578 16.5C20.7375 16.5 17.9578 18.5859 16.9266 21.5016C16.6505 22.2797 15.7931 22.6922 15.0122 22.4156C14.2313 22.1391 13.8216 21.2391 14.0977 20.4984C15.5386 16.4241 19.425 13.5 23.9578 13.5C27.3469 13.5 30.2859 15.0666 32.25 17.5031ZM14.25 33.75C13.4217 33.75 12.75 33.0797 12.75 32.25V27C12.75 26.1703 13.4217 25.5 14.25 25.5H19.5C20.3297 25.5 21 26.1703 21 27C21 27.8297 20.3297 28.5 19.5 28.5H17.9578C19.3687 30.3234 21.5484 31.5 24 31.5C27.2625 31.5 30.0422 29.4141 31.0734 26.4984C31.35 25.7203 32.2078 25.3078 32.9859 25.5844C33.7688 25.8609 34.1766 26.7188 33.9 27.5016C32.4609 31.575 28.575 34.5 24 34.5C20.6531 34.5 17.6719 32.9344 15.75 30.4969V32.25C15.75 33.0797 15.0783 33.75 14.25 33.75Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

11
assets/svg/sun-circle.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<g clip-path="url(#clip0_5813_29015)">
<path d="M16.5734 18.4328C16.8289 18.6892 17.1657 18.8173 17.5015 18.8173C17.8373 18.8173 18.1758 18.6898 18.4328 18.4328C18.9455 17.9201 18.9455 17.0897 18.4328 16.5773L15.9555 14.0999C15.4445 13.5872 14.6123 13.5872 14.0994 14.0999C13.5864 14.6126 13.5867 15.443 14.0994 15.956L16.5734 18.4328ZM24 16.125C24.7246 16.125 25.3125 15.5371 25.3125 14.8125V11.3125C25.3125 10.5879 24.7273 10 24 10C23.2727 10 22.6875 10.5879 22.6875 11.3125V14.8125C22.6875 15.5398 23.2781 16.125 24 16.125ZM16.125 24C16.125 23.2754 15.5371 22.6875 14.8125 22.6875H11.3125C10.5879 22.6875 10 23.2781 10 24C10 24.7219 10.5879 25.3125 11.3125 25.3125H14.8125C15.5398 25.3125 16.125 24.7273 16.125 24ZM30.4969 18.8156C30.8327 18.8156 31.1695 18.6874 31.4249 18.4311L33.8995 15.9549C34.4122 15.4422 34.4122 14.6117 33.8995 14.0988C33.3868 13.5858 32.5548 13.5861 32.0434 14.0988L29.5688 16.575C29.0561 17.0877 29.0561 17.9181 29.5688 18.4306C29.8242 18.6898 30.1633 18.8156 30.4969 18.8156ZM24 31.875C23.2754 31.875 22.6875 32.4629 22.6875 33.1875V36.6875C22.6875 37.4148 23.2781 38 24 38C24.7219 38 25.3125 37.4121 25.3125 36.6875V33.1875C25.3125 32.4656 24.7273 31.875 24 31.875ZM16.5734 29.5672L14.0988 32.0434C13.5861 32.5561 13.5861 33.3866 14.0988 33.8995C14.3552 34.1559 14.6911 34.284 15.0269 34.284C15.3627 34.284 15.6995 34.1559 15.9549 33.8995L18.4295 31.4233C18.9422 30.9106 18.9422 30.0802 18.4295 29.5677C17.9168 29.0553 17.0875 29.0531 16.5734 29.5672ZM36.6875 22.6875H33.1875C32.4629 22.6875 31.875 23.2754 31.875 24C31.875 24.7246 32.4629 25.3125 33.1875 25.3125H36.6875C37.4148 25.3125 38 24.7273 38 24C38 23.2727 37.4148 22.6875 36.6875 22.6875ZM31.4266 29.5672C30.9156 29.0545 30.0834 29.0547 29.5705 29.5674C29.0575 30.0801 29.0578 30.9105 29.5705 31.4229L32.0451 33.8992C32.3006 34.1555 32.6373 34.2837 32.9731 34.2837C33.3089 34.2837 33.6447 34.1555 33.9012 33.8992C34.4139 33.3865 34.4139 32.556 33.9012 32.0431L31.4266 29.5672ZM24 17.875C20.6148 17.875 17.875 20.6148 17.875 24C17.875 27.383 20.617 30.125 24 30.125C27.383 30.125 30.125 27.383 30.125 24C30.125 20.6148 27.3852 17.875 24 17.875Z" fill="#232323"/>
</g>
<defs>
<clipPath id="clip0_5813_29015">
<rect width="28" height="28" fill="white" transform="translate(10 10)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.25 2.83008C21.0105 2.83008 21.625 3.4165 21.625 4.1396C21.625 4.8627 21.0105 5.44913 20.25 5.44913H4.4375C4.05766 5.44913 3.75 5.74377 3.75 6.10389C3.75 6.46401 4.05766 6.75865 4.4375 6.75865H20.25C21.7668 6.75865 23 7.93313 23 9.3777V18.5444C23 19.9889 21.7668 21.1634 20.25 21.1634H3.75C2.23105 21.1634 1 19.9889 1 18.5444V5.44913C1 4.00251 2.23105 2.83008 3.75 2.83008H20.25ZM18.875 15.2706C19.6355 15.2706 20.25 14.6854 20.25 13.961C20.25 13.2367 19.6355 12.6515 18.875 12.6515C18.1145 12.6515 17.5 13.2367 17.5 13.961C17.5 14.6854 18.1145 15.2706 18.875 15.2706Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 702 B

@ -1 +1 @@
Subproject commit 51f74f05d465a92e0118cf7c2bcfb049df21af42
Subproject commit de29931dacc9aefaf42a9ca139a8754a42adc40d

View file

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
@ -255,6 +255,7 @@
"${BUILT_PRODUCTS_DIR}/cw_monero/cw_monero.framework",
"${BUILT_PRODUCTS_DIR}/cw_shared_external/cw_shared_external.framework",
"${BUILT_PRODUCTS_DIR}/cw_wownero/cw_wownero.framework",
"${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/devicelocale/devicelocale.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/flutter_libmonero/flutter_libmonero.framework",
@ -288,6 +289,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cw_monero.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cw_shared_external.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cw_wownero.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/devicelocale.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_libmonero.framework",
@ -454,7 +456,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 79;
CURRENT_PROJECT_VERSION = 83;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -508,7 +510,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.5.9;
MARKETING_VERSION = 1.5.11;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -641,7 +643,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 79;
CURRENT_PROJECT_VERSION = 83;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -695,7 +697,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.5.9;
MARKETING_VERSION = 1.5.11;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -720,7 +722,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 79;
CURRENT_PROJECT_VERSION = 83;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -774,7 +776,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.5.9;
MARKETING_VERSION = 1.5.11;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";

View file

@ -9,7 +9,6 @@ import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/notification_model.dart';
import 'package:stackwallet/models/trade_wallet_lookup.dart';
import 'package:stackwallet/services/wallets_service.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
@ -33,6 +32,7 @@ class DB {
static const String boxNamePriceCache = "priceAPIPrice24hCache";
static const String boxNameDBInfo = "dbInfo";
static const String boxNameTheme = "theme";
static const String boxNameDesktopData = "desktopData";
String boxNameTxCache({required Coin coin}) => "${coin.name}_txCache";
String boxNameSetCache({required Coin coin}) =>
@ -42,22 +42,23 @@ class DB {
static bool _initialized = false;
late final Box<dynamic> _boxAddressBook;
late final Box<String> _boxDebugInfo;
late final Box<NodeModel> _boxNodeModels;
late final Box<NodeModel> _boxPrimaryNodes;
late final Box<dynamic> _boxAllWalletsData;
late final Box<NotificationModel> _boxNotifications;
late final Box<NotificationModel> _boxWatchedTransactions;
late final Box<NotificationModel> _boxWatchedTrades;
late final Box<ExchangeTransaction> _boxTrades;
late final Box<Trade> _boxTradesV2;
late final Box<String> _boxTradeNotes;
late final Box<String> _boxFavoriteWallets;
late final Box<xmr.WalletInfo> _walletInfoSource;
late final Box<dynamic> _boxPrefs;
late final Box<TradeWalletLookup> _boxTradeLookup;
late final Box<dynamic> _boxDBInfo;
Box<dynamic>? _boxAddressBook;
Box<String>? _boxDebugInfo;
Box<NodeModel>? _boxNodeModels;
Box<NodeModel>? _boxPrimaryNodes;
Box<dynamic>? _boxAllWalletsData;
Box<NotificationModel>? _boxNotifications;
Box<NotificationModel>? _boxWatchedTransactions;
Box<NotificationModel>? _boxWatchedTrades;
Box<ExchangeTransaction>? _boxTrades;
Box<Trade>? _boxTradesV2;
Box<String>? _boxTradeNotes;
Box<String>? _boxFavoriteWallets;
Box<xmr.WalletInfo>? _walletInfoSource;
Box<dynamic>? _boxPrefs;
Box<TradeWalletLookup>? _boxTradeLookup;
Box<dynamic>? _boxDBInfo;
Box<String>? _boxDesktopData;
final Map<String, Box<dynamic>> _walletBoxes = {};
@ -66,7 +67,7 @@ class DB {
final Map<Coin, Box<dynamic>> _usedSerialsCacheBoxes = {};
// exposed for monero
Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource;
Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource!;
// mutex for stack backup
final mutex = Mutex();
@ -122,6 +123,12 @@ class DB {
_boxAllWalletsData = await Hive.openBox<dynamic>(boxNameAllWalletsData);
}
if (Hive.isBoxOpen(boxNameDesktopData)) {
_boxDesktopData = Hive.box<String>(boxNameDesktopData);
} else {
_boxDesktopData = await Hive.openBox<String>(boxNameDesktopData);
}
_boxNotifications =
await Hive.openBox<NotificationModel>(boxNameNotifications);
_boxWatchedTransactions =
@ -143,22 +150,11 @@ class DB {
_loadSharedCoinCacheBoxes(),
]);
_initialized = true;
try {
if (_boxPrefs.get("familiarity") == null) {
await _boxPrefs.put("familiarity", 0);
}
int count = _boxPrefs.get("familiarity") as int;
await _boxPrefs.put("familiarity", count + 1);
Constants.exchangeForExperiencedUsers(count + 1);
} catch (e, s) {
print("$e $s");
}
}
}
Future<void> _loadWalletBoxes() async {
final names = _boxAllWalletsData.get("names") as Map? ?? {};
final names = _boxAllWalletsData!.get("names") as Map? ?? {};
names.removeWhere((name, dyn) {
final jsonObject = Map<String, dynamic>.from(dyn as Map);
try {

View file

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:cw_core/node.dart';
import 'package:cw_core/unspent_coins_info.dart';
@ -10,6 +11,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_libmonero/monero/monero.dart';
import 'package:flutter_libmonero/wownero/wownero.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:isar/isar.dart';
@ -30,7 +32,8 @@ import 'package:stackwallet/pages/loading_view.dart';
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_login_view.dart';
import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart';
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
import 'package:stackwallet/providers/global/base_currencies_provider.dart';
// import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart';
@ -46,15 +49,16 @@ import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/notifications_service.dart';
import 'package:stackwallet/services/trade_service.dart';
import 'package:stackwallet/services/wallets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/db_version_migration.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/stack_file_system.dart';
import 'package:stackwallet/utilities/theme/color_theme.dart';
import 'package:stackwallet/utilities/theme/dark_colors.dart';
import 'package:stackwallet/utilities/theme/light_colors.dart';
import 'package:stackwallet/utilities/theme/ocean_breeze_colors.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:window_size/window_size.dart';
@ -68,26 +72,36 @@ final openedFromSWBFileStringStateProvider =
void main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
GoogleFonts.config.allowRuntimeFetching = false;
if (Platform.isIOS) {
Util.libraryPath = await getLibraryDirectory();
}
Screen? screen;
if (Platform.isLinux || Util.isDesktop) {
screen = await getCurrentScreen();
Util.screenWidth = screen?.frame.width;
}
if (Util.isDesktop) {
setWindowTitle('Stack Wallet');
setWindowMinSize(const Size(1200, 900));
setWindowMinSize(const Size(1220, 100));
setWindowMaxSize(Size.infinite);
final screenHeight = screen?.frame.height;
if (screenHeight != null) {
// starting to height be 3/4 screen height or 900, whichever is smaller
final height = min<double>(screenHeight * 0.75, 900);
setWindowFrame(
Rect.fromLTWH(0, 0, 1220, height),
);
}
}
Directory appDirectory = (await getApplicationDocumentsDirectory());
if (Platform.isIOS) {
appDirectory = (await getLibraryDirectory());
}
if (Platform.isLinux || Logging.isArmLinux) {
appDirectory = Directory("${appDirectory.path}/.stackwallet");
await appDirectory.create();
}
// FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
if (!(Logging.isArmLinux || Logging.isTestEnv)) {
final isar = await Isar.open(
[LogSchema],
directory: appDirectory.path,
directory: (await StackFileSystem.applicationIsarDirectory()).path,
inspector: false,
);
await Logging.instance.init(isar);
@ -136,18 +150,29 @@ void main() async {
Hive.registerAdapter(WalletTypeAdapter());
Hive.registerAdapter(UnspentCoinsInfoAdapter());
await Hive.initFlutter(appDirectory.path);
await Hive.initFlutter(
(await StackFileSystem.applicationHiveDirectory()).path);
await Hive.openBox<dynamic>(DB.boxNameDBInfo);
int dbVersion = DB.instance.get<dynamic>(
boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ??
0;
if (dbVersion < Constants.currentHiveDbVersion) {
try {
await DbVersionMigrator().migrate(dbVersion);
} catch (e, s) {
Logging.instance.log("Cannot migrate database\n$e $s",
level: LogLevel.Error, printFullLength: true);
// todo: db migrate stuff for desktop needs to be handled eventually
if (!Util.isDesktop) {
int dbVersion = DB.instance.get<dynamic>(
boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ??
0;
if (dbVersion < Constants.currentHiveDbVersion) {
try {
await DbVersionMigrator().migrate(
dbVersion,
secureStore: const SecureStorageWrapper(
store: FlutterSecureStorage(),
isDesktop: false,
),
);
} catch (e, s) {
Logging.instance.log("Cannot migrate database\n$e $s",
level: LogLevel.Error, printFullLength: true);
}
}
}
@ -195,8 +220,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
static const platform = MethodChannel("STACK_WALLET_RESTORE");
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
late final Wallets _wallets;
late final Prefs _prefs;
// late final Wallets _wallets;
// late final Prefs _prefs;
late final NotificationsService _notificationsService;
late final NodeService _nodeService;
late final TradesService _tradesService;
@ -204,58 +229,86 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
late final Completer<void> loadingCompleter;
bool didLoad = false;
bool didLoadShared = false;
bool _desktopHasPassword = false;
Future<void> load() async {
if (didLoad) {
Future<void> loadShared() async {
if (didLoadShared) {
return;
}
didLoad = true;
didLoadShared = true;
await DB.instance.init();
await _prefs.init();
await ref.read(prefsChangeNotifierProvider).init();
_notificationsService = ref.read(notificationsProvider);
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
_tradesService = ref.read(tradesServiceProvider);
final familiarity = ref.read(prefsChangeNotifierProvider).familiarity + 1;
ref.read(prefsChangeNotifierProvider).familiarity = familiarity;
NotificationApi.prefs = _prefs;
NotificationApi.notificationsService = _notificationsService;
Constants.exchangeForExperiencedUsers(familiarity);
unawaited(ref.read(baseCurrenciesProvider).update());
await _nodeService.updateDefaults();
await _notificationsService.init(
nodeService: _nodeService,
tradesService: _tradesService,
prefs: _prefs,
);
ref.read(priceAnd24hChangeNotifierProvider).start(true);
await _wallets.load(_prefs);
loadingCompleter.complete();
// TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet
// unawaited(_nodeService.updateCommunityNodes());
// run without awaiting
if (Constants.enableExchange &&
_prefs.externalCalls &&
await _prefs.isExternalCallsSet()) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
if (Util.isDesktop) {
_desktopHasPassword =
await ref.read(storageCryptoHandlerProvider).hasPassword();
}
}
if (_prefs.isAutoBackupEnabled) {
switch (_prefs.backupFrequencyType) {
case BackupFrequencyType.everyTenMinutes:
ref
.read(autoSWBServiceProvider)
.startPeriodicBackupTimer(duration: const Duration(minutes: 10));
break;
case BackupFrequencyType.everyAppStart:
unawaited(ref.read(autoSWBServiceProvider).doBackup());
break;
case BackupFrequencyType.afterClosingAWallet:
// ignore this case here
break;
Future<void> load() async {
try {
if (didLoad) {
return;
}
didLoad = true;
if (!Util.isDesktop) {
await loadShared();
}
_notificationsService = ref.read(notificationsProvider);
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
_tradesService = ref.read(tradesServiceProvider);
NotificationApi.prefs = ref.read(prefsChangeNotifierProvider);
NotificationApi.notificationsService = _notificationsService;
unawaited(ref.read(baseCurrenciesProvider).update());
await _nodeService.updateDefaults();
await _notificationsService.init(
nodeService: _nodeService,
tradesService: _tradesService,
prefs: ref.read(prefsChangeNotifierProvider),
);
ref.read(priceAnd24hChangeNotifierProvider).start(true);
await ref
.read(walletsChangeNotifierProvider)
.load(ref.read(prefsChangeNotifierProvider));
loadingCompleter.complete();
// TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet
// unawaited(_nodeService.updateCommunityNodes());
// run without awaiting
if (Constants.enableExchange &&
ref.read(prefsChangeNotifierProvider).externalCalls &&
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
}
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) {
switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) {
case BackupFrequencyType.everyTenMinutes:
ref.read(autoSWBServiceProvider).startPeriodicBackupTimer(
duration: const Duration(minutes: 10));
break;
case BackupFrequencyType.everyAppStart:
unawaited(ref.read(autoSWBServiceProvider).doBackup());
break;
case BackupFrequencyType.afterClosingAWallet:
// ignore this case here
break;
}
}
} catch (e, s) {
Logger.print("$e $s", normalLength: false);
}
}
@ -265,14 +318,17 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
final colorScheme = DB.instance
.get<dynamic>(boxName: DB.boxNameTheme, key: "colorScheme") as String?;
ThemeType themeType;
StackColorTheme colorTheme;
switch (colorScheme) {
case "dark":
themeType = ThemeType.dark;
colorTheme = DarkColors();
break;
case "oceanBreeze":
colorTheme = OceanBreezeColors();
break;
case "light":
default:
themeType = ThemeType.light;
colorTheme = LightColors();
}
loadingCompleter = Completer();
WidgetsBinding.instance.addObserver(this);
@ -281,13 +337,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
.read(localeServiceChangeNotifierProvider.notifier)
.loadLocale(notify: false);
_prefs = ref.read(prefsChangeNotifierProvider);
_wallets = ref.read(walletsChangeNotifierProvider);
WidgetsBinding.instance.addPostFrameCallback((_) async {
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
themeType == ThemeType.dark ? DarkColors() : LightColors());
StackColors.fromStackColorTheme(colorTheme);
if (Platform.isAndroid) {
// fetch open file if it exists
@ -388,7 +440,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
}
Future<void> goToRestoreSWB(String encrypted) async {
if (!_prefs.hasPin) {
if (!ref.read(prefsChangeNotifierProvider).hasPin) {
await Navigator.of(navigatorKey.currentContext!)
.pushNamed(CreatePinView.routeName, arguments: true)
.then((value) {
@ -534,49 +586,70 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
),
),
home: FutureBuilder(
future: load(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// FlutterNativeSplash.remove();
if (_wallets.hasWallets || _prefs.hasPin) {
// return HomeView();
home: Util.isDesktop
? FutureBuilder(
future: loadShared(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (_desktopHasPassword) {
String? startupWalletId;
if (ref
.read(prefsChangeNotifierProvider)
.gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
String? startupWalletId;
if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
return DesktopLoginView(
startupWalletId: startupWalletId,
load: load,
);
} else {
return const IntroView();
}
} else {
return const LoadingView();
}
},
)
: FutureBuilder(
future: load(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// FlutterNativeSplash.remove();
if (ref.read(walletsChangeNotifierProvider).hasWallets ||
ref.read(prefsChangeNotifierProvider).hasPin) {
// return HomeView();
// TODO proper desktop auth view
if (Util.isDesktop) {
Future<void>.delayed(Duration.zero).then((value) =>
Navigator.of(context).pushNamedAndRemoveUntil(
DesktopHomeView.routeName, (route) => false));
return Container();
}
String? startupWalletId;
if (ref
.read(prefsChangeNotifierProvider)
.gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
return LockscreenView(
isInitialAppLogin: true,
routeOnSuccess: HomeView.routeName,
routeOnSuccessArguments: startupWalletId,
biometricsAuthenticationTitle: "Unlock Stack",
biometricsLocalizedReason:
"Unlock your stack wallet using biometrics",
biometricsCancelButtonString: "Cancel",
);
} else {
return const IntroView();
}
} else {
// CURRENTLY DISABLED as cannot be animated
// technically not needed as FlutterNativeSplash will overlay
// anything returned here until the future completes but
// FutureBuilder requires you to return something
return const LoadingView();
}
},
),
return LockscreenView(
isInitialAppLogin: true,
routeOnSuccess: HomeView.routeName,
routeOnSuccessArguments: startupWalletId,
biometricsAuthenticationTitle: "Unlock Stack",
biometricsLocalizedReason:
"Unlock your stack wallet using biometrics",
biometricsCancelButtonString: "Cancel",
);
} else {
return const IntroView();
}
} else {
// CURRENTLY DISABLED as cannot be animated
// technically not needed as FlutterNativeSplash will overlay
// anything returned here until the future completes but
// FutureBuilder requires you to return something
return const LoadingView();
}
},
),
);
}
}

View file

@ -0,0 +1,18 @@
import 'package:isar/isar.dart';
part 'encrypted_string_value.g.dart';
@Collection()
class EncryptedStringValue {
Id id = Isar.autoIncrement;
@Index(unique: true, replace: true)
late String key;
late String value;
@override
String toString() {
return "EncryptedStringValue {\n key=$key\n value=$value\n}";
}
}

View file

@ -0,0 +1,748 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'encrypted_string_value.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, join_return_with_assignment, avoid_js_rounded_ints, prefer_final_locals
extension GetEncryptedStringValueCollection on Isar {
IsarCollection<EncryptedStringValue> get encryptedStringValues =>
this.collection();
}
const EncryptedStringValueSchema = CollectionSchema(
name: r'EncryptedStringValue',
id: 4826543019451092626,
properties: {
r'key': PropertySchema(
id: 0,
name: r'key',
type: IsarType.string,
),
r'value': PropertySchema(
id: 1,
name: r'value',
type: IsarType.string,
)
},
estimateSize: _encryptedStringValueEstimateSize,
serializeNative: _encryptedStringValueSerializeNative,
deserializeNative: _encryptedStringValueDeserializeNative,
deserializePropNative: _encryptedStringValueDeserializePropNative,
serializeWeb: _encryptedStringValueSerializeWeb,
deserializeWeb: _encryptedStringValueDeserializeWeb,
deserializePropWeb: _encryptedStringValueDeserializePropWeb,
idName: r'id',
indexes: {
r'key': IndexSchema(
id: -4906094122524121629,
name: r'key',
unique: true,
replace: true,
properties: [
IndexPropertySchema(
name: r'key',
type: IndexType.hash,
caseSensitive: true,
)
],
)
},
links: {},
embeddedSchemas: {},
getId: _encryptedStringValueGetId,
getLinks: _encryptedStringValueGetLinks,
attach: _encryptedStringValueAttach,
version: 5,
);
int _encryptedStringValueEstimateSize(
EncryptedStringValue object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.key.length * 3;
bytesCount += 3 + object.value.length * 3;
return bytesCount;
}
int _encryptedStringValueSerializeNative(
EncryptedStringValue object,
IsarBinaryWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeString(offsets[0], object.key);
writer.writeString(offsets[1], object.value);
return writer.usedBytes;
}
EncryptedStringValue _encryptedStringValueDeserializeNative(
int id,
IsarBinaryReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = EncryptedStringValue();
object.id = id;
object.key = reader.readString(offsets[0]);
object.value = reader.readString(offsets[1]);
return object;
}
P _encryptedStringValueDeserializePropNative<P>(
Id id,
IsarBinaryReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readString(offset)) as P;
case 1:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Object _encryptedStringValueSerializeWeb(
IsarCollection<EncryptedStringValue> collection,
EncryptedStringValue object) {
/*final jsObj = IsarNative.newJsObject();*/ throw UnimplementedError();
}
EncryptedStringValue _encryptedStringValueDeserializeWeb(
IsarCollection<EncryptedStringValue> collection, Object jsObj) {
/*final object = EncryptedStringValue();object.id = IsarNative.jsObjectGet(jsObj, r'id') ?? (double.negativeInfinity as int);object.key = IsarNative.jsObjectGet(jsObj, r'key') ?? '';object.value = IsarNative.jsObjectGet(jsObj, r'value') ?? '';*/
//return object;
throw UnimplementedError();
}
P _encryptedStringValueDeserializePropWeb<P>(
Object jsObj, String propertyName) {
switch (propertyName) {
default:
throw IsarError('Illegal propertyName');
}
}
int? _encryptedStringValueGetId(EncryptedStringValue object) {
if (object.id == Isar.autoIncrement) {
return null;
} else {
return object.id;
}
}
List<IsarLinkBase<dynamic>> _encryptedStringValueGetLinks(
EncryptedStringValue object) {
return [];
}
void _encryptedStringValueAttach(
IsarCollection<dynamic> col, Id id, EncryptedStringValue object) {
object.id = id;
}
extension EncryptedStringValueByIndex on IsarCollection<EncryptedStringValue> {
Future<EncryptedStringValue?> getByKey(String key) {
return getByIndex(r'key', [key]);
}
EncryptedStringValue? getByKeySync(String key) {
return getByIndexSync(r'key', [key]);
}
Future<bool> deleteByKey(String key) {
return deleteByIndex(r'key', [key]);
}
bool deleteByKeySync(String key) {
return deleteByIndexSync(r'key', [key]);
}
Future<List<EncryptedStringValue?>> getAllByKey(List<String> keyValues) {
final values = keyValues.map((e) => [e]).toList();
return getAllByIndex(r'key', values);
}
List<EncryptedStringValue?> getAllByKeySync(List<String> keyValues) {
final values = keyValues.map((e) => [e]).toList();
return getAllByIndexSync(r'key', values);
}
Future<int> deleteAllByKey(List<String> keyValues) {
final values = keyValues.map((e) => [e]).toList();
return deleteAllByIndex(r'key', values);
}
int deleteAllByKeySync(List<String> keyValues) {
final values = keyValues.map((e) => [e]).toList();
return deleteAllByIndexSync(r'key', values);
}
Future<int> putByKey(EncryptedStringValue object) {
return putByIndex(r'key', object);
}
int putByKeySync(EncryptedStringValue object, {bool saveLinks = true}) {
return putByIndexSync(r'key', object, saveLinks: saveLinks);
}
Future<List<int>> putAllByKey(List<EncryptedStringValue> objects) {
return putAllByIndex(r'key', objects);
}
List<int> putAllByKeySync(List<EncryptedStringValue> objects,
{bool saveLinks = true}) {
return putAllByIndexSync(r'key', objects, saveLinks: saveLinks);
}
}
extension EncryptedStringValueQueryWhereSort
on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QWhere> {
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhere>
anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension EncryptedStringValueQueryWhere
on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QWhereClause> {
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
idEqualTo(int id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: id,
upper: id,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
idNotEqualTo(int id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
idGreaterThan(int id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
idLessThan(int id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
idBetween(
int lowerId,
int upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
keyEqualTo(String key) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'key',
value: [key],
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
keyNotEqualTo(String key) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'key',
lower: [],
upper: [key],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'key',
lower: [key],
includeLower: false,
upper: [],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'key',
lower: [key],
includeLower: false,
upper: [],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'key',
lower: [],
upper: [key],
includeUpper: false,
));
}
});
}
}
extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
EncryptedStringValue, QFilterCondition> {
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> idEqualTo(int value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: value,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> idGreaterThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> idLessThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> idBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> keyEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'key',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> keyGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'key',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> keyLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'key',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> keyBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'key',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> keyStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'key',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> keyEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'key',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition>
keyContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'key',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition>
keyMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'key',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> valueEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'value',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> valueGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'value',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> valueLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'value',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> valueBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'value',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> valueStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'value',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition> valueEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'value',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition>
valueContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'value',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
QAfterFilterCondition>
valueMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'value',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
}
extension EncryptedStringValueQueryObject on QueryBuilder<EncryptedStringValue,
EncryptedStringValue, QFilterCondition> {}
extension EncryptedStringValueQueryLinks on QueryBuilder<EncryptedStringValue,
EncryptedStringValue, QFilterCondition> {}
extension EncryptedStringValueQuerySortBy
on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QSortBy> {
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
sortByKey() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'key', Sort.asc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
sortByKeyDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'key', Sort.desc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
sortByValue() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'value', Sort.asc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
sortByValueDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'value', Sort.desc);
});
}
}
extension EncryptedStringValueQuerySortThenBy
on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QSortThenBy> {
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
thenByKey() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'key', Sort.asc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
thenByKeyDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'key', Sort.desc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
thenByValue() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'value', Sort.asc);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy>
thenByValueDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'value', Sort.desc);
});
}
}
extension EncryptedStringValueQueryWhereDistinct
on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QDistinct> {
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QDistinct>
distinctByKey({bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'key', caseSensitive: caseSensitive);
});
}
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QDistinct>
distinctByValue({bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'value', caseSensitive: caseSensitive);
});
}
}
extension EncryptedStringValueQueryProperty on QueryBuilder<
EncryptedStringValue, EncryptedStringValue, QQueryProperty> {
QueryBuilder<EncryptedStringValue, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<EncryptedStringValue, String, QQueryOperations> keyProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'key');
});
}
QueryBuilder<EncryptedStringValue, String, QQueryOperations> valueProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'value');
});
}
}

View file

@ -1,4 +1,5 @@
import 'package:hive/hive.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
part 'type_adaptors/node_model.g.dart';
@ -65,8 +66,7 @@ class NodeModel {
}
/// convenience getter to retrieve login password
Future<String?> getPassword(
FlutterSecureStorageInterface secureStorage) async {
Future<String?> getPassword(SecureStorageInterface secureStorage) async {
return await secureStorage.read(key: "${id}_nodePW");
}
@ -85,7 +85,7 @@ class NodeModel {
return map;
}
bool get isDefault => id.startsWith("default_");
bool get isDefault => id.startsWith(DefaultNodes.defaultNodeIdPrefix);
@override
String toString() {

View file

@ -2,6 +2,7 @@ import 'package:dart_numerics/dart_numerics.dart';
import 'package:decimal/decimal.dart';
import 'package:hive/hive.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
part '../type_adaptors/transactions_model.g.dart';
@ -220,14 +221,16 @@ class Transaction {
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
txType: json['txType'] as String,
amount: (Decimal.parse(json["amount"].toString()) *
Decimal.fromInt(Constants.satsPerCoin))
Decimal.fromInt(Constants.satsPerCoin(Coin
.firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure
.toBigInt()
.toInt(),
aliens: [],
worthNow: json['worthNow'] as String,
worthAtBlockTimestamp: json['worthAtBlockTimestamp'] as String? ?? "0",
fees: (Decimal.parse(json["fees"].toString()) *
Decimal.fromInt(Constants.satsPerCoin))
Decimal.fromInt(Constants.satsPerCoin(Coin
.firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure
.toBigInt()
.toInt(),
inputSize: json['inputSize'] as int? ?? 0,
@ -386,7 +389,8 @@ class Output {
scriptpubkeyType: json['scriptPubKey']['type'] as String?,
scriptpubkeyAddress: address,
value: (Decimal.parse(json["value"].toString()) *
Decimal.fromInt(Constants.satsPerCoin))
Decimal.fromInt(Constants.satsPerCoin(Coin
.firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure
.toBigInt()
.toInt(),
);

View file

@ -1,14 +1,16 @@
class TransactionFilter {
final bool sent;
final bool received;
final DateTime from;
final DateTime to;
final bool trade;
final DateTime? from;
final DateTime? to;
final int? amount;
final String keyword;
TransactionFilter({
required this.sent,
required this.received,
required this.trade,
required this.from,
required this.to,
required this.amount,
@ -18,6 +20,7 @@ class TransactionFilter {
TransactionFilter copyWith({
bool? sent,
bool? received,
bool? trade,
DateTime? from,
DateTime? to,
int? amount,
@ -26,6 +29,7 @@ class TransactionFilter {
return TransactionFilter(
sent: sent ?? this.sent,
received: received ?? this.received,
trade: trade ?? this.trade,
from: from ?? this.from,
to: to ?? this.to,
amount: amount ?? this.amount,
@ -35,6 +39,6 @@ class TransactionFilter {
@override
String toString() {
return "TxFilter { sent: $sent, received: $received, from: $from, to: $to, amount: $amount, keyword: $keyword }";
return "TxFilter { sent: $sent, received: $received, trade: $trade, from: $from, to: $to, amount: $amount, keyword: $keyword }";
}
}

View file

@ -4,6 +4,8 @@ import 'package:stackwallet/models/notification_model.dart';
import 'package:stackwallet/utilities/format.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';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -20,22 +22,33 @@ class NotificationCard extends StatelessWidget {
return Format.extractDateFrom(date.millisecondsSinceEpoch ~/ 1000);
}
static const double mobileIconSize = 24;
static const double desktopIconSize = 30;
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
return Stack(
children: [
RoundedWhiteContainer(
padding: isDesktop
? const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
)
: const EdgeInsets.all(12),
child: Row(
children: [
notification.changeNowId == null
? SvgPicture.asset(
notification.iconAssetName,
width: 24,
height: 24,
width: isDesktop ? desktopIconSize : mobileIconSize,
height: isDesktop ? desktopIconSize : mobileIconSize,
)
: Container(
width: 24,
height: 24,
width: isDesktop ? desktopIconSize : mobileIconSize,
height: isDesktop ? desktopIconSize : mobileIconSize,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(24),
@ -45,8 +58,8 @@ class NotificationCard extends StatelessWidget {
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 24,
height: 24,
width: isDesktop ? desktopIconSize : mobileIconSize,
height: isDesktop ? desktopIconSize : mobileIconSize,
),
),
const SizedBox(
@ -56,9 +69,35 @@ class NotificationCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
notification.title,
style: STextStyles.titleBold12(context),
ConditionalParent(
condition: isDesktop && !notification.read,
builder: (child) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
child,
Text(
"New",
style:
STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen,
),
)
],
),
child: Text(
notification.title,
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
)
: STextStyles.titleBold12(context),
),
),
const SizedBox(
height: 2,
@ -68,11 +107,25 @@ class NotificationCard extends StatelessWidget {
children: [
Text(
notification.description,
style: STextStyles.label(context),
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
)
: STextStyles.label(context),
),
Text(
extractPrettyDateString(notification.date),
style: STextStyles.label(context),
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
)
: STextStyles.label(context),
),
],
),

View file

@ -6,6 +6,8 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
export 'package:stackwallet/utilities/enums/flush_bar_type.dart';
Future<dynamic> showFloatingFlushBar({
required FlushBarType type,
required String message,

View file

@ -11,6 +11,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.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/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
@ -90,6 +91,8 @@ class _AddWalletViewState extends State<AddWalletView> {
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchFieldController,
focusNode: _searchFocusNode,
onChanged: (value) {
@ -180,40 +183,43 @@ class _AddWalletViewState extends State<AddWalletView> {
),
);
} else {
return Scaffold(
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
),
),
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const AddWalletText(
isDesktop: false,
),
const SizedBox(
height: 16,
),
Expanded(
child: MobileCoinList(
coins: coins,
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const AddWalletText(
isDesktop: false,
),
),
const SizedBox(
height: 16,
),
const AddWalletNextButton(
isDesktop: false,
),
],
const SizedBox(
height: 16,
),
Expanded(
child: MobileCoinList(
coins: coins,
),
),
const SizedBox(
height: 16,
),
const AddWalletNextButton(
isDesktop: false,
),
],
),
),
),
),

View file

@ -8,11 +8,9 @@ class MobileCoinList extends StatelessWidget {
const MobileCoinList({
Key? key,
required this.coins,
required this.isDesktop,
}) : super(key: key);
final List<Coin> coins;
final bool isDesktop;
@override
Widget build(BuildContext context) {

View file

@ -26,11 +26,17 @@ class SearchableCoinList extends ConsumerWidget {
e.name.toLowerCase().contains(lowercaseTerm));
}
if (!showTestNetCoins) {
_coins.removeWhere((e) => e.name.endsWith("TestNet"));
_coins.removeWhere(
(e) => e.name.endsWith("TestNet") || e == Coin.bitcoincashTestnet);
}
// remove firo testnet regardless
_coins.remove(Coin.firoTestNet);
// Kidgloves for Wownero on desktop
if(isDesktop) {
_coins.remove(Coin.wownero);
}
return _coins;
}

View file

@ -7,6 +7,7 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/exit_to_my
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
@ -77,49 +78,53 @@ class CreateOrRestoreWalletView extends StatelessWidget {
),
);
} else {
return Scaffold(
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
),
),
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(31),
child: CoinImage(
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(31),
child: CoinImage(
coin: coin,
isDesktop: isDesktop,
),
),
const Spacer(
flex: 2,
),
CreateRestoreWalletTitle(
coin: coin,
isDesktop: isDesktop,
),
),
const Spacer(
flex: 2,
),
CreateRestoreWalletTitle(
coin: coin,
isDesktop: isDesktop,
),
const SizedBox(
height: 8,
),
CreateRestoreWalletSubTitle(
isDesktop: isDesktop,
),
const Spacer(
flex: 5,
),
CreateWalletButtonGroup(
coin: coin,
isDesktop: isDesktop,
),
],
const SizedBox(
height: 8,
),
CreateRestoreWalletSubTitle(
isDesktop: isDesktop,
),
const Spacer(
flex: 5,
),
CreateWalletButtonGroup(
coin: coin,
isDesktop: isDesktop,
),
],
),
),
),
),

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/name_generator.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/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
@ -108,40 +109,44 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
),
);
} else {
return Scaffold(
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
if (textFieldFocusNode.hasFocus) {
textFieldFocusNode.unfocus();
Future<void>.delayed(const Duration(milliseconds: 100))
.then((value) => Navigator.of(context).pop());
} else {
if (mounted) {
Navigator.of(context).pop();
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
if (textFieldFocusNode.hasFocus) {
textFieldFocusNode.unfocus();
Future<void>.delayed(const Duration(milliseconds: 100))
.then((value) => Navigator.of(context).pop());
} else {
if (mounted) {
Navigator.of(context).pop();
}
}
}
},
),
),
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (ctx, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: _content(),
),
),
);
},
),
),
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (ctx, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: _content(),
),
),
);
},
),
),
),
),
);
}
@ -194,6 +199,8 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
onChanged: (string) {
if (string.isEmpty) {
if (_nextEnabled) {

View file

@ -16,7 +16,6 @@ import 'package:stackwallet/services/coins/manager.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/flush_bar_type.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';

View file

@ -6,10 +6,12 @@ class MnemonicTable extends StatelessWidget {
Key? key,
required this.words,
required this.isDesktop,
this.itemBorderColor,
}) : super(key: key);
final List<String> words;
final bool isDesktop;
final Color? itemBorderColor;
@override
Widget build(BuildContext context) {
@ -40,6 +42,7 @@ class MnemonicTable extends StatelessWidget {
number: ++index,
word: words[index - 1],
isDesktop: isDesktop,
borderColor: itemBorderColor,
),
),
],
@ -61,6 +64,7 @@ class MnemonicTable extends StatelessWidget {
number: i + 1,
word: words[i],
isDesktop: isDesktop,
borderColor: itemBorderColor,
),
),
],

View file

@ -9,16 +9,19 @@ class MnemonicTableItem extends StatelessWidget {
required this.number,
required this.word,
required this.isDesktop,
this.borderColor,
}) : super(key: key);
final int number;
final String word;
final bool isDesktop;
final Color? borderColor;
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return RoundedWhiteContainer(
borderColor: borderColor,
padding: isDesktop
? const EdgeInsets.symmetric(horizontal: 12, vertical: 9)
: const EdgeInsets.all(8),

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/exit_to_my_stack_button.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/coins/manager.dart';
@ -241,6 +242,7 @@ class _NewWalletRecoveryPhraseWarningViewState
coin,
walletId,
walletName,
ref.read(secureStoreProvider),
node,
txTracker,
ref.read(prefsChangeNotifierProvider),

View file

@ -23,6 +23,8 @@ import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart'
as datePicker;
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
@ -152,7 +154,7 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
await Future<void>.delayed(const Duration(milliseconds: 125));
}
final date = await showRoundedDatePicker(
final date = await datePicker.showRoundedDatePicker(
context: context,
initialDate: DateTime.now(),
height: height * 0.5,
@ -252,7 +254,11 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
SizedBox(
height: isDesktop ? 40 : 24,
),
if (coin == Coin.monero || coin == Coin.epicCash)
if (coin == Coin.monero ||
coin == Coin.epicCash ||
(coin == Coin.wownero &&
ref.watch(mnemonicWordCountStateProvider.state).state ==
25))
Text(
"Choose start date",
style: isDesktop
@ -264,11 +270,19 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
if (coin == Coin.monero || coin == Coin.epicCash)
if (coin == Coin.monero ||
coin == Coin.epicCash ||
(coin == Coin.wownero &&
ref.watch(mnemonicWordCountStateProvider.state).state ==
25))
SizedBox(
height: isDesktop ? 16 : 8,
),
if (coin == Coin.monero || coin == Coin.epicCash)
if (coin == Coin.monero ||
coin == Coin.epicCash ||
(coin == Coin.wownero &&
ref.watch(mnemonicWordCountStateProvider.state).state ==
25))
// if (!isDesktop)
RestoreFromDatePicker(
@ -278,11 +292,19 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
// if (isDesktop)
// // TODO desktop date picker
if (coin == Coin.monero || coin == Coin.epicCash)
if (coin == Coin.monero ||
coin == Coin.epicCash ||
(coin == Coin.wownero &&
ref.watch(mnemonicWordCountStateProvider.state).state ==
25))
const SizedBox(
height: 8,
),
if (coin == Coin.monero || coin == Coin.epicCash)
if (coin == Coin.monero ||
coin == Coin.epicCash ||
(coin == Coin.wownero &&
ref.watch(mnemonicWordCountStateProvider.state).state ==
25))
RoundedWhiteContainer(
child: Center(
child: Text(
@ -299,7 +321,11 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
),
),
),
if (coin == Coin.monero || coin == Coin.epicCash)
if (coin == Coin.monero ||
coin == Coin.epicCash ||
(coin == Coin.wownero &&
ref.watch(mnemonicWordCountStateProvider.state).state ==
25))
SizedBox(
height: isDesktop ? 24 : 16,
),

View file

@ -7,6 +7,8 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
class MobileMnemonicLengthSelector extends ConsumerWidget {
const MobileMnemonicLengthSelector({
Key? key,
@ -19,7 +21,9 @@ class MobileMnemonicLengthSelector extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return Stack(
children: [
const TextField(
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
// controller: _lengthController,
readOnly: true,
textInputAction: TextInputAction.none,

View file

@ -4,6 +4,8 @@ import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
class RestoreFromDatePicker extends StatefulWidget {
const RestoreFromDatePicker({
Key? key,
@ -35,6 +37,8 @@ class _RestoreFromDatePickerState extends State<RestoreFromDatePicker> {
return Container(
color: Colors.transparent,
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
onTap: onTap,
controller: _dateController,
style: STextStyles.field(context),

View file

@ -8,6 +8,7 @@ import 'package:bip39/src/wordlists/english.dart' as bip39wordlist;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_libmonero/monero/monero.dart';
import 'package:flutter_libmonero/wownero/wownero.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
@ -18,6 +19,7 @@ import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widge
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/exit_to_my_stack_button.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/coins/manager.dart';
@ -149,12 +151,18 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
super.dispose();
}
// TODO: check for wownero wordlist?
bool _isValidMnemonicWord(String word) {
// TODO: get the actual language
if (widget.coin == Coin.monero) {
var moneroWordList = monero.getMoneroWordList("English");
return moneroWordList.contains(word);
}
if (widget.coin == Coin.wownero) {
var wowneroWordList = wownero.getWowneroWordList("English",
seedWordsLength: widget.seedWordsLength);
return wowneroWordList.contains(word);
}
return _wordListHashSet.contains(word);
}
@ -180,7 +188,13 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
if (widget.coin == Coin.monero) {
height = monero.getHeigthByDate(date: widget.restoreFromDate);
} else if (widget.coin == Coin.wownero) {
height = wownero.getHeightByDate(date: widget.restoreFromDate);
}
// todo: wait until this implemented
// else if (widget.coin == Coin.wownero) {
// height = wownero.getHeightByDate(date: widget.restoreFromDate);
// }
// TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index
if (widget.coin == Coin.epicCash) {
@ -260,6 +274,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
widget.coin,
walletId,
widget.walletName,
ref.read(secureStoreProvider),
node,
txTracker,
ref.read(prefsChangeNotifierProvider),

View file

@ -13,20 +13,27 @@ 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';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/address_book_card.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/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class AddressBookView extends ConsumerStatefulWidget {
const AddressBookView({Key? key, this.coin}) : super(key: key);
const AddressBookView({
Key? key,
this.coin,
this.filterTerm,
}) : super(key: key);
static const String routeName = "/addressBook";
final Coin? coin;
final String? filterTerm;
@override
ConsumerState<AddressBookView> createState() => _AddressBookViewState();
@ -37,9 +44,6 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
final _searchFocusNode = FocusNode();
List<Contact>? _cache;
List<Contact>? _cacheFav;
String _searchTerm = "";
@override
@ -48,8 +52,7 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
ref.refresh(addressBookFilterProvider);
if (widget.coin == null) {
List<Coin> coins =
Coin.values.where((e) => !(e == Coin.epicCash)).toList();
List<Coin> coins = Coin.values.toList();
coins.remove(Coin.firoTestNet);
bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins;
@ -57,8 +60,9 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
if (showTestNet) {
ref.read(addressBookFilterProvider).addAll(coins, false);
} else {
ref.read(addressBookFilterProvider).addAll(
coins.getRange(0, coins.length - kTestNetCoinCount + 1), false);
ref
.read(addressBookFilterProvider)
.addAll(coins.getRange(0, coins.length - kTestNetCoinCount), false);
}
} else {
ref.read(addressBookFilterProvider).add(widget.coin!, false);
@ -98,289 +102,276 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final addressBookEntriesFuture = ref.watch(
addressBookServiceProvider.select((value) => value.addressBookEntries));
final contacts =
ref.watch(addressBookServiceProvider.select((value) => value.contacts));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Address book",
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("addressBookFilterViewButton"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.filter,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pushNamed(
AddressBookFilterView.routeName,
);
Navigator.of(context).pop();
},
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("addressBookAddNewContactViewButton"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
Navigator.of(context).pushNamed(
AddAddressBookEntryView.routeName,
);
},
title: Text(
"Address book",
style: STextStyles.navBarTitle(context),
),
),
),
],
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
actions: [
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("addressBookFilterViewButton"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.background,
icon: SvgPicture.asset(
Assets.svg.filter,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
Navigator.of(context).pushNamed(
AddressBookFilterView.routeName,
);
},
),
),
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("addressBookAddNewContactViewButton"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.background,
icon: SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
Navigator.of(context).pushNamed(
AddAddressBookEntryView.routeName,
);
},
),
),
),
],
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight:
MediaQuery.of(context).size.height - 271,
),
child: child,
),
child: TextField(
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (value) {
setState(() {
_searchTerm = value;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
),
),
);
},
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: !isDesktop
? TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (value) {
setState(() {
_searchTerm = value;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
_searchTerm = "";
});
},
),
],
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
)
: null,
),
)
: null,
),
if (!isDesktop) const SizedBox(height: 16),
Text(
"Favorites",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
if (contacts.isNotEmpty)
RoundedWhiteContainer(
padding: EdgeInsets.all(!isDesktop ? 0 : 15),
child: Column(
children: [
...contacts
.where((element) => element.addresses
.where((e) => ref.watch(addressBookFilterProvider
.select((value) => value.coins.contains(e.coin))))
.isNotEmpty)
.where((e) =>
e.isFavorite &&
ref
.read(addressBookServiceProvider)
.matches(widget.filterTerm ?? _searchTerm, e))
.where((element) => element.isFavorite)
.map(
(e) => AddressBookCard(
key: Key("favContactCard_${e.id}_key"),
contactId: e.id,
),
),
],
),
),
if (contacts.isEmpty)
RoundedWhiteContainer(
child: Center(
child: Text(
"Your favorite contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
),
const SizedBox(
height: 16,
),
Text(
"All contacts",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
if (contacts.isNotEmpty)
Column(
children: [
RoundedWhiteContainer(
padding: EdgeInsets.all(!isDesktop ? 0 : 15),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
...contacts
.where((element) => element.addresses
.where((e) => ref.watch(
addressBookFilterProvider.select((value) =>
value.coins.contains(e.coin))))
.isNotEmpty)
.where((e) => ref
.read(addressBookServiceProvider)
.matches(widget.filterTerm ?? _searchTerm, e))
.map(
(e) => AddressBookCard(
key: Key("desktopContactCard_${e.id}_key"),
contactId: e.id,
),
),
),
),
const SizedBox(
height: 16,
),
Text(
"Favorites",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: addressBookEntriesFuture,
builder: (_, AsyncSnapshot<List<Contact>> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
_cacheFav = snapshot.data!;
}
if (_cacheFav == null) {
// TODO proper loading animation
return const LoadingIndicator();
} else {
if (_cacheFav!.isNotEmpty) {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cacheFav!
.where((element) => element.addresses
.where((e) => ref.watch(
addressBookFilterProvider
.select((value) => value
.coins
.contains(e.coin))))
.isNotEmpty)
.where((e) =>
e.isFavorite &&
ref
.read(
addressBookServiceProvider)
.matches(_searchTerm, e))
.where(
(element) => element.isFavorite)
.map(
(e) => AddressBookCard(
key: Key(
"favContactCard_${e.id}_key"),
contactId: e.id,
),
),
],
),
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"Your favorite contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
);
}
}
},
),
const SizedBox(
height: 16,
),
Text(
"All contacts",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: addressBookEntriesFuture,
builder: (_, AsyncSnapshot<List<Contact>> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
_cache = snapshot.data!;
}
if (_cache == null) {
// TODO proper loading animation
return const LoadingIndicator();
} else {
if (_cache!.isNotEmpty) {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cache!
.where((element) => element.addresses
.where((e) => ref.watch(
addressBookFilterProvider
.select((value) => value
.coins
.contains(e.coin))))
.isNotEmpty)
.where((e) => ref
.read(addressBookServiceProvider)
.matches(_searchTerm, e))
.where(
(element) => !element.isFavorite)
.map(
(e) => AddressBookCard(
key: Key(
"contactCard_${e.id}_key"),
contactId: e.id,
),
),
],
),
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"Your contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
);
}
}
},
),
],
),
),
),
],
),
if (contacts.isEmpty)
RoundedWhiteContainer(
child: Center(
child: Text(
"Your contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
),
);
},
],
),
);
}

View file

@ -15,8 +15,15 @@ import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
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/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/emoji_select_sheet.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
@ -106,393 +113,615 @@ class _AddAddressBookEntryViewState
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"New contact",
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("addAddressBookEntryFavoriteButtonKey"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.star,
color: _isFavorite
? Theme.of(context)
.extension<StackColors>()!
.favoriteStarActive
: Theme.of(context)
.extension<StackColors>()!
.favoriteStarInactive,
width: 20,
height: 20,
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
});
},
),
),
),
],
),
body: LayoutBuilder(
builder: (context, constraint) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: SingleChildScrollView(
controller: scrollController,
padding: const EdgeInsets.only(
// top: 8,
left: 4,
right: 4,
bottom: 16,
),
child: ConstrainedBox(
constraints: BoxConstraints(
// subtract top and bottom padding set in parent
minHeight: constraint.maxHeight - 16, // - 8,
title: Text(
"New contact",
style: STextStyles.navBarTitle(context),
),
child: IntrinsicHeight(
child: Column(
children: [
const SizedBox(
height: 4,
),
GestureDetector(
onTap: () {
if (_selectedEmoji != null) {
setState(() {
_selectedEmoji = null;
});
return;
}
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) => const EmojiSelectSheet(),
).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
actions: [
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("addAddressBookEntryFavoriteButtonKey"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.background,
icon: SvgPicture.asset(
Assets.svg.star,
color: _isFavorite
? Theme.of(context)
.extension<StackColors>()!
.favoriteStarActive
: Theme.of(context)
.extension<StackColors>()!
.favoriteStarInactive,
width: 20,
height: 20,
),
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
});
},
child: SizedBox(
height: 48,
width: 48,
child: Stack(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
_selectedEmoji!.char,
style:
STextStyles.pageTitleH1(context),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 8,
height: 8,
),
),
),
)
],
),
),
),
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
],
),
body: child),
);
},
child: ConditionalParent(
condition: isDesktop,
builder: (child) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Text(
"New contact",
style: STextStyles.desktopH3(context),
textAlign: TextAlign.center,
),
child: TextField(
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
suffixIcon: ref
.read(contactNameIsNotEmptyStateProvider
.state)
.state
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController.text = "";
});
},
),
],
),
),
)
: null,
),
onChanged: (newValue) {
ref
.read(contactNameIsNotEmptyStateProvider.state)
.state = newValue.isNotEmpty;
},
),
),
if (forms.length <= 1)
const SizedBox(
height: 8,
),
if (forms.length <= 1) forms[0],
if (forms.length > 1)
for (int i = 0; i < forms.length; i++)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Row(
],
),
),
const DesktopDialogCloseButton(),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 10,
right: 10,
bottom: 32,
),
child: child,
),
),
],
);
},
child: LayoutBuilder(
builder: (context, constraint) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: SingleChildScrollView(
controller: scrollController,
padding: EdgeInsets.only(
// top: 8,
left: 4,
right: 4,
bottom: isDesktop ? 0 : 16,
),
child: ConstrainedBox(
constraints: BoxConstraints(
// subtract top and bottom padding set in parent
minHeight:
constraint.maxHeight - (isDesktop ? 0 : 16), // - 8,
),
child: IntrinsicHeight(
child: Column(
children: [
if (!isDesktop) const SizedBox(height: 4),
isDesktop
? Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Address ${i + 1}",
style: STextStyles.smallMed12(context),
SizedBox(
height: 56,
width: 56,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
if (_selectedEmoji != null) {
setState(() {
_selectedEmoji = null;
});
return;
}
showDialog<dynamic>(
context: context,
builder: (context) {
return const DesktopDialog(
maxHeight: 700,
maxWidth: 600,
child: Padding(
padding: EdgeInsets.only(
left: 32,
right: 20,
top: 32,
bottom: 32,
),
child: EmojiSelectSheet(),
),
);
}).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
},
child: Stack(
children: [
Container(
height: 56,
width: 56,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(100),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: 30,
width: 30,
)
: Text(
_selectedEmoji!.char,
style: STextStyles
.pageTitleH1(
context),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
14),
color: Theme.of(context)
.extension<
StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(
context)
.extension<
StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(
context)
.extension<
StackColors>()!
.textWhite,
width: 8,
height: 8,
),
),
),
)
],
),
),
),
),
BlueTextButton(
const SizedBox(width: 8),
SizedBox(
width: isDesktop ? 450 : null,
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect:
Util.isDesktop ? false : true,
enableSuggestions:
Util.isDesktop ? false : true,
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state
? Padding(
padding:
const EdgeInsets.only(
right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController
.text = "";
});
},
),
],
),
),
)
: null,
),
onChanged: (newValue) {
ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state = newValue.isNotEmpty;
},
),
),
),
],
)
: Column(
children: [
GestureDetector(
onTap: () {
_removeForm(forms[i].id);
if (_selectedEmoji != null) {
setState(() {
_selectedEmoji = null;
});
return;
}
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) =>
const EmojiSelectSheet(),
).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
},
text: "Remove",
child: SizedBox(
height: 48,
width: 48,
child: Stack(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
_selectedEmoji!.char,
style: STextStyles
.pageTitleH1(context),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(14),
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<
StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(context)
.extension<
StackColors>()!
.textWhite,
width: 8,
height: 8,
),
),
),
)
],
),
),
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect:
Util.isDesktop ? false : true,
enableSuggestions:
Util.isDesktop ? false : true,
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
suffixIcon: ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state
? Padding(
padding: const EdgeInsets.only(
right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController
.text = "";
});
},
),
],
),
),
)
: null,
),
onChanged: (newValue) {
ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state = newValue.isNotEmpty;
},
),
),
],
),
const SizedBox(
height: 8,
),
forms[i],
],
),
const SizedBox(
height: 16,
),
BlueTextButton(
onTap: () {
_addForm();
scrollController.animateTo(
scrollController.position.maxScrollExtent + 500,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
text: "+ Add another address",
),
// GestureDetector(
//
// child: Text(
// "+ Add another address",
// style: STextStyles.largeMedium14(context),
// ),
// ),
const SizedBox(
height: 16,
),
const Spacer(),
Row(
children: [
Expanded(
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(height: 8),
if (forms.length <= 1)
const SizedBox(
width: 16,
height: 8,
),
Expanded(
child: Builder(
builder: (context) {
bool nameExists = ref
.watch(contactNameIsNotEmptyStateProvider
.state)
.state;
bool validForms = ref.watch(
validContactStateProvider(forms
.map((e) => e.id)
.toList(growable: false)));
bool shouldEnableSave =
validForms && nameExists;
return TextButton(
style: shouldEnableSave
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(
context),
onPressed: shouldEnableSave
? () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75),
);
}
List<ContactAddressEntry> entries =
[];
for (int i = 0;
i < forms.length;
i++) {
entries.add(ref
.read(addressEntryDataProvider(
forms[i].id))
.buildAddressEntry());
}
Contact contact = Contact(
emojiChar: _selectedEmoji?.char,
name: nameController.text,
addresses: entries,
isFavorite: _isFavorite,
);
if (await ref
.read(addressBookServiceProvider)
.addContact(contact)) {
if (mounted) {
Navigator.of(context).pop();
}
// TODO show success notification
} else {
// TODO show error notification
}
}
: null,
child: Text(
"Save",
style: STextStyles.button(context).copyWith(
color: shouldEnableSave
? Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimary
: Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimaryDisabled,
if (forms.length <= 1) forms[0],
if (forms.length > 1)
for (int i = 0; i < forms.length; i++)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Address ${i + 1}",
style: STextStyles.smallMed12(context),
),
),
);
},
BlueTextButton(
onTap: () {
_removeForm(forms[i].id);
},
text: "Remove",
),
],
),
const SizedBox(
height: 8,
),
forms[i],
],
),
),
],
),
],
const SizedBox(
height: 16,
),
BlueTextButton(
onTap: () {
_addForm();
scrollController.animateTo(
scrollController.position.maxScrollExtent + 500,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
text: "+ Add another address",
),
// GestureDetector(
//
// child: Text(
// "+ Add another address",
// style: STextStyles.largeMedium14(context),
// ),
// ),
const SizedBox(
height: 16,
),
const Spacer(),
Row(
children: [
Expanded(
child: SecondaryButton(
label: "Cancel",
buttonHeight: isDesktop ? ButtonHeight.m : null,
onPressed: () async {
if (!isDesktop &&
FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75),
);
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: Builder(
builder: (context) {
bool nameExists = ref
.watch(contactNameIsNotEmptyStateProvider
.state)
.state;
bool validForms = ref.watch(
validContactStateProvider(forms
.map((e) => e.id)
.toList(growable: false)));
bool shouldEnableSave =
validForms && nameExists;
return PrimaryButton(
label: "Save",
buttonHeight:
isDesktop ? ButtonHeight.m : null,
enabled: shouldEnableSave,
onPressed: shouldEnableSave
? () async {
if (FocusScope.of(context)
.hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(
milliseconds: 75),
);
}
List<ContactAddressEntry> entries =
[];
for (int i = 0;
i < forms.length;
i++) {
entries.add(ref
.read(
addressEntryDataProvider(
forms[i].id))
.buildAddressEntry());
}
Contact contact = Contact(
emojiChar: _selectedEmoji?.char,
name: nameController.text,
addresses: entries,
isFavorite: _isFavorite,
);
if (await ref
.read(
addressBookServiceProvider)
.addContact(contact)) {
if (mounted) {
Navigator.of(context).pop();
}
// TODO show success notification
} else {
// TODO show error notification
}
}
: null,
);
},
),
),
],
),
],
),
),
),
),
),
);
},
);
},
),
),
);
}

View file

@ -12,7 +12,12 @@ import 'package:stackwallet/utilities/barcode_scanner_interface.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/utilities/util.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/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
class AddNewContactAddressView extends ConsumerStatefulWidget {
const AddNewContactAddressView({
@ -55,190 +60,173 @@ class _AddNewContactAddressViewState
final contact = ref.watch(addressBookServiceProvider
.select((value) => value.getContactById(contactId)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Add new address",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
title: Text(
"Add new address",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
children: [
Row(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: contact.emojiChar == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
contact.emojiChar!,
style: STextStyles.pageTitleH1(context),
),
),
),
const SizedBox(
width: 16,
),
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
contact.name,
style: STextStyles.pageTitleH2(context),
),
),
),
],
),
const SizedBox(
height: 16,
),
NewContactAddressEntryForm(
id: 0,
barcodeScanner: barcodeScanner,
clipboard: clipboard,
),
const SizedBox(
height: 16,
),
const Spacer(),
Row(
children: [
Expanded(
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: Builder(
builder: (context) {
bool shouldEnableSave =
ref.watch(validContactStateProvider([0]));
return TextButton(
style: shouldEnableSave
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(
context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(
context),
onPressed: shouldEnableSave
? () async {
if (FocusScope.of(context)
.hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(
milliseconds: 75),
);
}
List<ContactAddressEntry> entries =
contact.addresses;
entries.add(ref
.read(
addressEntryDataProvider(0))
.buildAddressEntry());
Contact editedContact = contact
.copyWith(addresses: entries);
if (await ref
.read(
addressBookServiceProvider)
.editContact(editedContact)) {
if (mounted) {
Navigator.of(context).pop();
}
// TODO show success notification
} else {
// TODO show error notification
}
}
: null,
child: Text(
"Save",
style: STextStyles.button(context),
),
);
},
),
),
],
)
],
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
),
child: Column(
children: [
Row(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: contact.emojiChar == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
contact.emojiChar!,
style: STextStyles.pageTitleH1(context),
),
),
),
),
);
},
const SizedBox(
width: 16,
),
if (isDesktop)
Text(
contact.name,
style: STextStyles.pageTitleH2(context),
),
if (!isDesktop)
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
contact.name,
style: STextStyles.pageTitleH2(context),
),
),
),
],
),
const SizedBox(
height: 16,
),
NewContactAddressEntryForm(
id: 0,
barcodeScanner: barcodeScanner,
clipboard: clipboard,
),
const SizedBox(
height: 16,
),
const Spacer(),
Row(
children: [
Expanded(
child: SecondaryButton(
label: "Cancel",
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: () async {
if (!isDesktop && FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Save",
enabled: ref.watch(validContactStateProvider([0])),
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75),
);
}
List<ContactAddressEntry> entries = contact.addresses;
entries.add(ref
.read(addressEntryDataProvider(0))
.buildAddressEntry());
Contact editedContact =
contact.copyWith(addresses: entries);
if (await ref
.read(addressBookServiceProvider)
.editContact(editedContact)) {
if (mounted) {
Navigator.of(context).pop();
}
// TODO show success notification
} else {
// TODO show error notification
}
},
),
),
],
)
],
),
);
}

View file

@ -5,7 +5,13 @@ import 'package:stackwallet/providers/ui/address_book_providers/address_book_fil
import 'package:stackwallet/utilities/enums/coin_enum.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class AddressBookFilterView extends ConsumerStatefulWidget {
@ -33,7 +39,7 @@ class _AddressBookFilterViewState extends ConsumerState<AddressBookFilterView> {
} else {
_coins = coins
.toList(growable: false)
.getRange(0, coins.length - kTestNetCoinCount + 1)
.getRange(0, coins.length - kTestNetCoinCount)
.toList(growable: false);
}
super.initState();
@ -41,167 +47,226 @@ class _AddressBookFilterViewState extends ConsumerState<AddressBookFilterView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
"Filter addresses",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(12),
child: LayoutBuilder(builder: (builderContext, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
child: Text(
"Only selected cryptocurrency addresses will be displayed.",
style: STextStyles.itemSubtitle(context),
),
),
const SizedBox(
height: 12,
),
Text(
"Select cryptocurrency",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Wrap(
title: Text(
"Filter addresses",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(12),
child: LayoutBuilder(builder: (builderContext, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
..._coins.map(
(coin) => Row(
children: [
GestureDetector(
onTap: () {
if (ref
.read(addressBookFilterProvider)
.coins
.contains(coin)) {
ref
.read(addressBookFilterProvider)
.remove(coin, true);
} else {
ref
.read(addressBookFilterProvider)
.add(coin, true);
}
setState(() {});
},
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
height: 20,
width: 20,
child: Checkbox(
value: ref
.watch(
addressBookFilterProvider
.select((value) =>
value.coins))
.contains(coin),
onChanged: (value) {
if (value is bool) {
if (value) {
ref
.read(
addressBookFilterProvider)
.add(coin, true);
} else {
ref
.read(
addressBookFilterProvider)
.remove(coin, true);
}
setState(() {});
}
},
),
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
coin.prettyName,
style:
STextStyles.largeMedium14(
context),
),
const SizedBox(
height: 2,
),
Text(
coin.ticker,
style:
STextStyles.itemSubtitle(
context),
),
],
)
],
),
),
),
),
],
RoundedWhiteContainer(
child: Text(
"Only selected cryptocurrency addresses will be displayed.",
style: STextStyles.itemSubtitle(context),
),
),
const SizedBox(
height: 12,
),
Text(
"Select cryptocurrency",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
child,
],
),
),
const Spacer(),
// Row(
// children: [
// TextButton(
// onPressed: () {},
// child: Text("Cancel"),
// ),
// SizedBox(
// width: 16,
// ),
// TextButton(
// onPressed: () {},
// child: Text("Cancel"),
// ),
// ],
// )
],
),
),
);
}),
),
),
);
},
child: ConditionalParent(
condition: isDesktop,
builder: (child) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(32),
child: Text(
"Select cryptocurrency",
style: STextStyles.desktopH3(context),
textAlign: TextAlign.center,
),
),
const DesktopDialogCloseButton(),
],
),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 32),
child: child,
),
],
),
),
),
);
},
),
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 32, vertical: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SecondaryButton(
width: 248,
buttonHeight: ButtonHeight.l,
enabled: true,
label: "Cancel",
onPressed: () {
Navigator.of(context).pop();
},
),
// const SizedBox(width: 16),
PrimaryButton(
width: 248,
buttonHeight: ButtonHeight.l,
enabled: true,
label: "Apply",
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
),
],
);
}),
},
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Wrap(
children: [
..._coins.map(
(coin) => Row(
children: [
GestureDetector(
onTap: () {
if (ref
.read(addressBookFilterProvider)
.coins
.contains(coin)) {
ref
.read(addressBookFilterProvider)
.remove(coin, true);
} else {
ref.read(addressBookFilterProvider).add(coin, true);
}
setState(() {});
},
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 20,
width: 20,
child: Checkbox(
value: ref
.watch(addressBookFilterProvider
.select((value) => value.coins))
.contains(coin),
onChanged: (value) {
if (value is bool) {
if (value) {
ref
.read(addressBookFilterProvider)
.add(coin, true);
} else {
ref
.read(addressBookFilterProvider)
.remove(coin, true);
}
setState(() {});
}
},
),
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
coin.prettyName,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
coin.ticker,
style: STextStyles.itemSubtitle(context),
),
],
)
],
),
),
),
),
],
),
),
],
),
),
),
);
}

View file

@ -18,6 +18,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
@ -104,335 +105,203 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
final _contact = ref.watch(addressBookServiceProvider
.select((value) => value.getContactById(_contactId)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Contact details",
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("contactDetails"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.star,
color: _contact.isFavorite
? Theme.of(context)
.extension<StackColors>()!
.favoriteStarActive
: Theme.of(context)
.extension<StackColors>()!
.favoriteStarInactive,
width: 20,
height: 20,
),
onPressed: () {
bool isFavorite = _contact.isFavorite;
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Contact details",
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("contactDetails"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.star,
color: _contact.isFavorite
? Theme.of(context)
.extension<StackColors>()!
.favoriteStarActive
: Theme.of(context)
.extension<StackColors>()!
.favoriteStarInactive,
width: 20,
height: 20,
),
onPressed: () {
bool isFavorite = _contact.isFavorite;
ref
.read(addressBookServiceProvider)
.editContact(_contact.copyWith(isFavorite: !isFavorite));
},
ref.read(addressBookServiceProvider).editContact(
_contact.copyWith(isFavorite: !isFavorite));
},
),
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("contactDetailsViewDeleteContactButtonKey"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.trash,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
showDialog<dynamic>(
context: context,
useSafeArea: true,
barrierDismissible: true,
builder: (_) => StackDialog(
title: "Delete ${_contact.name}?",
message: "Contact will be deleted permanently!",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.itemSubtitle12(context),
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("contactDetailsViewDeleteContactButtonKey"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.trash,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
showDialog<dynamic>(
context: context,
useSafeArea: true,
barrierDismissible: true,
builder: (_) => StackDialog(
title: "Delete ${_contact.name}?",
message: "Contact will be deleted permanently!",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.itemSubtitle12(context),
),
onPressed: () {
Navigator.of(context).pop();
},
),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Delete",
style: STextStyles.button(context),
),
onPressed: () {
ref
.read(addressBookServiceProvider)
.removeContact(_contact.id);
Navigator.of(context).pop();
Navigator.of(context).pop();
showFloatingFlushBar(
type: FlushBarType.success,
message: "${_contact.name} deleted",
context: context,
);
},
),
onPressed: () {
Navigator.of(context).pop();
},
),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Delete",
style: STextStyles.button(context),
);
},
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Row(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _contact.emojiChar == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
_contact.emojiChar!,
style: STextStyles.pageTitleH1(context),
),
),
),
const SizedBox(
width: 16,
),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
_contact.name,
textAlign: TextAlign.left,
style: STextStyles.pageTitleH2(context),
),
),
const Spacer(),
TextButton(
onPressed: () {
ref
.read(addressBookServiceProvider)
.removeContact(_contact.id);
Navigator.of(context).pop();
Navigator.of(context).pop();
showFloatingFlushBar(
type: FlushBarType.success,
message: "${_contact.name} deleted",
context: context,
Navigator.of(context).pushNamed(
EditContactNameEmojiView.routeName,
arguments: _contact.id,
);
},
),
),
);
},
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Row(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
style: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _contact.emojiChar == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
_contact.emojiChar!,
style: STextStyles.pageTitleH1(context),
),
),
),
const SizedBox(
width: 16,
),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
_contact.name,
textAlign: TextAlign.left,
style: STextStyles.pageTitleH2(context),
),
),
const Spacer(),
TextButton(
onPressed: () {
Navigator.of(context).pushNamed(
EditContactNameEmojiView.routeName,
arguments: _contact.id,
);
},
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context)!
.copyWith(
minimumSize: MaterialStateProperty.all<Size>(
const Size(46, 32)),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
SvgPicture.asset(Assets.svg.pencil,
width: 10,
height: 10,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
const SizedBox(
width: 4,
.getSecondaryEnabledButtonColor(context)!
.copyWith(
minimumSize: MaterialStateProperty.all<Size>(
const Size(46, 32)),
),
Text(
"Edit",
style: STextStyles.buttonSmall(context),
),
],
),
),
),
],
),
const SizedBox(
height: 24,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Addresses",
style: STextStyles.itemSubtitle(context),
),
BlueTextButton(
text: "Add new",
onTap: () {
Navigator.of(context).pushNamed(
AddNewContactAddressView.routeName,
arguments: _contact.id,
);
},
),
],
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._contact.addresses.map(
(e) => Padding(
padding: const EdgeInsets.all(12),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: e.coin),
height: 24,
),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${e.label} (${e.coin.ticker})",
style:
STextStyles.itemSubtitle12(context),
),
const SizedBox(
height: 2,
),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
e.address,
style: STextStyles.itemSubtitle(context)
.copyWith(
fontSize: 8,
),
),
),
],
),
),
GestureDetector(
onTap: () {
ref
.read(addressEntryDataProvider(0))
.address = e.address;
ref
.read(addressEntryDataProvider(0))
.addressLabel = e.label;
ref.read(addressEntryDataProvider(0)).coin =
e.coin;
Navigator.of(context).pushNamed(
EditContactAddressView.routeName,
arguments: Tuple2(_contact.id, e),
);
},
child: RoundedContainer(
SvgPicture.asset(Assets.svg.pencil,
width: 10,
height: 10,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.pencil,
width: 14,
height: 14,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
.accentColorDark),
const SizedBox(
width: 4,
),
GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(text: e.address),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.copy,
width: 16,
height: 16,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
Text(
"Edit",
style: STextStyles.buttonSmall(context),
),
],
),
@ -440,81 +309,216 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
),
],
),
),
const SizedBox(
height: 24,
),
Text(
"Transaction history",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: _filteredTransactionsByContact(
ref.watch(walletsChangeNotifierProvider).managers),
builder: (_,
AsyncSnapshot<List<Tuple2<String, Transaction>>>
snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_cachedTransactions = snapshot.data!;
if (_cachedTransactions.isNotEmpty) {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cachedTransactions.map(
(e) => TransactionCard(
key: Key(
"contactDetailsTransaction_${e.item2.txid}_cardKey"),
transaction: e.item2,
walletId: e.item1,
const SizedBox(
height: 24,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Addresses",
style: STextStyles.itemSubtitle(context),
),
BlueTextButton(
text: "Add new",
onTap: () {
Navigator.of(context).pushNamed(
AddNewContactAddressView.routeName,
arguments: _contact.id,
);
},
),
],
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._contact.addresses.map(
(e) => Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: e.coin),
height: 24,
),
),
],
),
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"No transactions found",
style: STextStyles.itemSubtitle(context),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"${e.label} (${e.coin.ticker})",
style:
STextStyles.itemSubtitle12(context),
),
const SizedBox(
height: 2,
),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
e.address,
style:
STextStyles.itemSubtitle(context)
.copyWith(
fontSize: 8,
),
),
),
],
),
),
GestureDetector(
onTap: () {
ref
.read(addressEntryDataProvider(0))
.address = e.address;
ref
.read(addressEntryDataProvider(0))
.addressLabel = e.label;
ref.read(addressEntryDataProvider(0)).coin =
e.coin;
Navigator.of(context).pushNamed(
EditContactAddressView.routeName,
arguments: Tuple2(_contact.id, e),
);
},
child: RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.pencil,
width: 14,
height: 14,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
const SizedBox(
width: 4,
),
GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(text: e.address),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.copy,
width: 16,
height: 16,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
],
),
),
);
}
} else {
// TODO: proper loading animation
if (_cachedTransactions.isEmpty) {
return const LoadingIndicator();
} else {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cachedTransactions.map(
(e) => TransactionCard(
key: Key(
"contactDetailsTransaction_${e.item2.txid}_cardKey"),
transaction: e.item2,
walletId: e.item1,
),
],
),
),
const SizedBox(
height: 24,
),
Text(
"Transaction history",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: _filteredTransactionsByContact(
ref.watch(walletsChangeNotifierProvider).managers),
builder: (_,
AsyncSnapshot<List<Tuple2<String, Transaction>>>
snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_cachedTransactions = snapshot.data!;
if (_cachedTransactions.isNotEmpty) {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cachedTransactions.map(
(e) => TransactionCard(
key: Key(
"contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"),
transaction: e.item2,
walletId: e.item1,
),
),
],
),
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"No transactions found",
style: STextStyles.itemSubtitle(context),
),
],
),
);
),
);
}
} else {
// TODO: proper loading animation
if (_cachedTransactions.isEmpty) {
return const LoadingIndicator();
} else {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cachedTransactions.map(
(e) => TransactionCard(
key: Key(
"contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"),
transaction: e.item2,
walletId: e.item1,
),
),
],
),
);
}
}
}
},
),
const SizedBox(
height: 16,
),
],
},
),
const SizedBox(
height: 16,
),
],
),
),
),
),

View file

@ -12,7 +12,12 @@ import 'package:stackwallet/utilities/barcode_scanner_interface.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/utilities/util.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/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
class EditContactAddressView extends ConsumerStatefulWidget {
const EditContactAddressView({
@ -44,6 +49,42 @@ class _EditContactAddressViewState
late final BarcodeScannerInterface barcodeScanner;
late final ClipboardInterface clipboard;
Future<void> save(Contact contact) async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75),
);
}
List<ContactAddressEntry> entries = contact.addresses.toList();
final entry = entries.firstWhere(
(e) =>
e.label == addressEntry.label &&
e.address == addressEntry.address &&
e.coin == addressEntry.coin,
);
final index = entries.indexOf(entry);
entries.remove(entry);
ContactAddressEntry editedEntry =
ref.read(addressEntryDataProvider(0)).buildAddressEntry();
entries.insert(index, editedEntry);
Contact editedContact = contact.copyWith(addresses: entries);
if (await ref.read(addressBookServiceProvider).editContact(editedContact)) {
if (mounted) {
Navigator.of(context).pop();
}
// TODO show success notification
} else {
// TODO show error notification
}
}
@override
void initState() {
contactId = widget.contactId;
@ -59,236 +100,184 @@ class _EditContactAddressViewState
final contact = ref.watch(addressBookServiceProvider
.select((value) => value.getContactById(contactId)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Edit address",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
final bool isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
title: Text(
"Edit address",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
children: [
Row(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: contact.emojiChar == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
contact.emojiChar!,
style: STextStyles.pageTitleH1(context),
),
),
),
const SizedBox(
width: 16,
),
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
contact.name,
style: STextStyles.pageTitleH2(context),
),
),
),
],
),
const SizedBox(
height: 16,
),
NewContactAddressEntryForm(
id: 0,
barcodeScanner: barcodeScanner,
clipboard: clipboard,
),
const SizedBox(
height: 24,
),
GestureDetector(
onTap: () async {
// delete address
final _addresses = contact.addresses;
final entry = _addresses.firstWhere(
(e) =>
e.label == addressEntry.label &&
e.address == addressEntry.address &&
e.coin == addressEntry.coin,
);
_addresses.remove(entry);
Contact editedContact =
contact.copyWith(addresses: _addresses);
if (await ref
.read(addressBookServiceProvider)
.editContact(editedContact)) {
Navigator.of(context).pop();
// TODO show success notification
} else {
// TODO show error notification
}
},
child: Text(
"Delete address",
style: STextStyles.link(context),
),
),
const Spacer(),
const SizedBox(
height: 16,
),
Row(
children: [
Expanded(
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: Builder(
builder: (context) {
bool shouldEnableSave =
ref.watch(validContactStateProvider([0]));
return TextButton(
style: shouldEnableSave
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(
context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(
context),
onPressed: shouldEnableSave
? () async {
if (FocusScope.of(context)
.hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(
milliseconds: 75),
);
}
List<ContactAddressEntry> entries =
contact.addresses.toList();
final entry = entries.firstWhere(
(e) =>
e.label ==
addressEntry.label &&
e.address ==
addressEntry.address &&
e.coin == addressEntry.coin,
);
final index =
entries.indexOf(entry);
entries.remove(entry);
ContactAddressEntry editedEntry = ref
.read(
addressEntryDataProvider(0))
.buildAddressEntry();
entries.insert(index, editedEntry);
Contact editedContact = contact
.copyWith(addresses: entries);
if (await ref
.read(
addressBookServiceProvider)
.editContact(editedContact)) {
if (mounted) {
Navigator.of(context).pop();
}
// TODO show success notification
} else {
// TODO show error notification
}
}
: null,
child: Text(
"Save",
style: STextStyles.button(context),
),
);
},
),
),
],
),
],
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
),
child: Column(
children: [
Row(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: contact.emojiChar == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
contact.emojiChar!,
style: STextStyles.pageTitleH1(context),
),
),
),
const SizedBox(
width: 16,
),
if (isDesktop)
Text(
contact.name,
style: STextStyles.pageTitleH2(context),
),
if (!isDesktop)
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
contact.name,
style: STextStyles.pageTitleH2(context),
),
),
),
],
),
const SizedBox(
height: 16,
),
NewContactAddressEntryForm(
id: 0,
barcodeScanner: barcodeScanner,
clipboard: clipboard,
),
const SizedBox(
height: 24,
),
ConditionalParent(
condition: isDesktop,
builder: (child) => MouseRegion(
cursor: SystemMouseCursors.click,
child: child,
),
child: GestureDetector(
onTap: () async {
// delete address
final _addresses = contact.addresses;
final entry = _addresses.firstWhere(
(e) =>
e.label == addressEntry.label &&
e.address == addressEntry.address &&
e.coin == addressEntry.coin,
);
_addresses.remove(entry);
Contact editedContact = contact.copyWith(addresses: _addresses);
if (await ref
.read(addressBookServiceProvider)
.editContact(editedContact)) {
Navigator.of(context).pop();
// TODO show success notification
} else {
// TODO show error notification
}
},
child: Text(
"Delete address",
style: STextStyles.link(context),
),
),
);
},
),
const Spacer(),
const SizedBox(
height: 16,
),
Row(
children: [
Expanded(
child: SecondaryButton(
label: "Cancel",
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: () async {
if (!isDesktop && FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Save",
enabled: ref.watch(validContactStateProvider([0])),
onPressed: () => save(contact),
buttonHeight: isDesktop ? ButtonHeight.l : null,
),
),
],
),
],
),
);
}

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:emojis/emoji.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -7,7 +9,13 @@ import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/emoji_select_sheet.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
@ -67,266 +75,326 @@ class _EditContactNameEmojiViewState
final contact = ref.watch(addressBookServiceProvider
.select((value) => value.getContactById(contactId)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Edit contact",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
children: [
GestureDetector(
onTap: () {
if (_selectedEmoji != null) {
setState(() {
_selectedEmoji = null;
});
return;
}
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) => const EmojiSelectSheet(),
).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
},
child: SizedBox(
height: 48,
width: 48,
child: Stack(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
_selectedEmoji!.char,
style: STextStyles.pageTitleH1(
context),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 8,
height: 8,
),
),
),
)
],
),
),
),
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
suffixIcon: nameController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
const Spacer(),
const SizedBox(
height: 16,
),
Row(
children: [
Expanded(
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: Builder(
builder: (context) {
bool shouldEnableSave =
nameController.text.isNotEmpty;
final isDesktop = Util.isDesktop;
final double emojiSize = isDesktop ? 56 : 48;
return TextButton(
style: shouldEnableSave
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(
context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(
context),
onPressed: shouldEnableSave
? () async {
if (FocusScope.of(context)
.hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(
milliseconds: 75),
);
}
final editedContact =
contact.copyWith(
shouldCopyEmojiWithNull: true,
name: nameController.text,
emojiChar: _selectedEmoji == null
? null
: _selectedEmoji!.char,
);
ref
.read(
addressBookServiceProvider)
.editContact(
editedContact,
);
if (mounted) {
Navigator.of(context).pop();
}
}
: null,
child: Text(
"Save",
style: STextStyles.button(context),
),
);
},
),
),
],
)
],
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Edit contact",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
if (_selectedEmoji != null) {
setState(() {
_selectedEmoji = null;
});
return;
}
if (isDesktop) {
showDialog<dynamic>(
barrierColor: Colors.transparent,
context: context,
builder: (context) {
return const DesktopDialog(
maxHeight: 700,
maxWidth: 600,
child: Padding(
padding: EdgeInsets.only(
left: 32,
right: 20,
top: 32,
bottom: 32,
),
child: EmojiSelectSheet(),
),
);
}).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
} else {
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) => const EmojiSelectSheet(),
).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
}
},
child: SizedBox(
height: emojiSize,
width: emojiSize,
child: Stack(
children: [
Container(
height: emojiSize,
width: emojiSize,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(emojiSize / 2),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: emojiSize / 2,
width: emojiSize / 2,
)
: Text(
_selectedEmoji!.char,
style: isDesktop
? STextStyles.desktopH3(context)
: STextStyles.pageTitleH1(context),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 8,
height: 8,
),
),
),
)
],
),
),
),
if (isDesktop)
const SizedBox(
width: 8,
),
if (isDesktop)
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
suffixIcon: nameController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
),
],
),
if (!isDesktop)
const SizedBox(
height: 8,
),
if (!isDesktop)
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
suffixIcon: nameController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
);
},
const Spacer(),
const SizedBox(
height: 16,
),
Row(
children: [
Expanded(
child: SecondaryButton(
label: "Cancel",
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: () async {
if (!isDesktop && FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Save",
enabled: nameController.text.isNotEmpty,
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: () async {
if (!isDesktop && FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75),
);
}
final editedContact = contact.copyWith(
shouldCopyEmojiWithNull: true,
name: nameController.text,
emojiChar:
_selectedEmoji == null ? null : _selectedEmoji!.char,
);
unawaited(
ref.read(addressBookServiceProvider).editContact(
editedContact,
),
);
if (mounted) {
Navigator.of(context).pop();
}
},
),
),
],
)
],
),
);
}

View file

@ -1,8 +1,10 @@
import 'package:dropdown_button2/dropdown_button2.dart';
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:stackwallet/pages/address_book_views/subviews/coin_select_sheet.dart';
import 'package:stackwallet/providers/providers.dart';
// import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart';
import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart';
import 'package:stackwallet/utilities/address_utils.dart';
@ -14,6 +16,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.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/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
@ -46,6 +49,8 @@ class _NewContactAddressEntryFormState
late final FocusNode addressLabelFocusNode;
late final FocusNode addressFocusNode;
List<Coin> coins = [];
@override
void initState() {
addressLabelController = TextEditingController()
@ -54,6 +59,7 @@ class _NewContactAddressEntryFormState
..text = ref.read(addressEntryDataProvider(widget.id)).address ?? "";
addressLabelFocusNode = FocusNode();
addressFocusNode = FocusNode();
coins = [...Coin.values];
super.initState();
}
@ -68,84 +74,180 @@ class _NewContactAddressEntryFormState
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
bool showTestNet = ref.watch(
prefsChangeNotifierProvider.select((value) => value.showTestNetCoins),
);
if (isDesktop) {
coins = [...Coin.values];
coins.remove(Coin.firoTestNet);
if (showTestNet) {
coins = coins.sublist(0, coins.length - kTestNetCoinCount);
}
}
return Column(
children: [
TextField(
readOnly: true,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Select cryptocurrency",
hintStyle: STextStyles.fieldLabel(context),
prefixIcon: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: RawMaterialButton(
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
if (isDesktop)
DropdownButtonHideUnderline(
child: DropdownButton2<Coin>(
hint: Text(
"Select cryptocurrency",
style: STextStyles.fieldLabel(context),
),
offset: const Offset(0, -10),
isExpanded: true,
dropdownElevation: 0,
value: ref.watch(addressEntryDataProvider(widget.id)
.select((value) => value.coin)),
onChanged: (value) {
if (value is Coin) {
ref.read(addressEntryDataProvider(widget.id)).coin = value;
}
},
icon: SvgPicture.asset(
Assets.svg.chevronDown,
width: 10,
height: 5,
color: Theme.of(context).extension<StackColors>()!.textDark3,
),
buttonPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
buttonDecoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
dropdownDecoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
items: [
...coins.map(
(coin) => DropdownMenuItem<Coin>(
value: coin,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
height: 24,
width: 24,
),
const SizedBox(
width: 12,
),
Text(
coin.prettyName,
style:
STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
],
),
),
),
onPressed: () {
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
builder: (_) => const CoinSelectSheet(),
).then((value) {
if (value is Coin) {
ref.read(addressEntryDataProvider(widget.id)).coin =
value;
}
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ref.watch(addressEntryDataProvider(widget.id)
.select((value) => value.coin)) ==
null
? Text(
"Select cryptocurrency",
style: STextStyles.fieldLabel(context),
)
: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(
coin: ref.watch(
addressEntryDataProvider(widget.id)
.select((value) => value.coin))!),
height: 20,
width: 20,
),
const SizedBox(
width: 12,
),
Text(
ref
.watch(addressEntryDataProvider(widget.id)
.select((value) => value.coin))!
.prettyName,
style: STextStyles.itemSubtitle12(context),
),
],
),
SvgPicture.asset(
Assets.svg.chevronDown,
width: 8,
height: 4,
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle2,
),
],
),
),
if (!isDesktop)
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
readOnly: true,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Select cryptocurrency",
hintStyle: STextStyles.fieldLabel(context),
prefixIcon: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: RawMaterialButton(
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
],
),
onPressed: () {
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
builder: (_) => const CoinSelectSheet(),
).then((value) {
if (value is Coin) {
ref.read(addressEntryDataProvider(widget.id)).coin =
value;
}
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ref.watch(addressEntryDataProvider(widget.id)
.select((value) => value.coin)) ==
null
? Text(
"Select cryptocurrency",
style: STextStyles.fieldLabel(context),
)
: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(
coin: ref.watch(
addressEntryDataProvider(widget.id)
.select(
(value) => value.coin))!),
height: 20,
width: 20,
),
const SizedBox(
width: 12,
),
Text(
ref
.watch(
addressEntryDataProvider(widget.id)
.select((value) => value.coin))!
.prettyName,
style: STextStyles.itemSubtitle12(context),
),
],
),
if (!isDesktop)
SvgPicture.asset(
Assets.svg.chevronDown,
width: 8,
height: 4,
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle2,
),
],
),
),
),
),
),
),
),
const SizedBox(
height: 8,
),
@ -154,6 +256,8 @@ class _NewContactAddressEntryFormState
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
focusNode: addressLabelFocusNode,
controller: addressLabelController,
style: STextStyles.field(context),
@ -162,6 +266,7 @@ class _NewContactAddressEntryFormState
addressLabelFocusNode,
context,
).copyWith(
labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
suffixIcon: addressLabelController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
@ -197,6 +302,7 @@ class _NewContactAddressEntryFormState
Constants.size.circularBorderRadius,
),
child: TextField(
enableSuggestions: Util.isDesktop ? false : true,
focusNode: addressFocusNode,
controller: addressController,
style: STextStyles.field(context),
@ -205,6 +311,7 @@ class _NewContactAddressEntryFormState
addressFocusNode,
context,
).copyWith(
labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
suffixIcon: UnconstrainedBox(
child: Row(
children: [
@ -244,9 +351,10 @@ class _NewContactAddressEntryFormState
},
child: const ClipboardIcon(),
),
if (ref.watch(addressEntryDataProvider(widget.id)
.select((value) => value.address)) ==
null)
if (!Util.isDesktop &&
ref.watch(addressEntryDataProvider(widget.id)
.select((value) => value.address)) ==
null)
TextFieldIconButton(
key: const Key("addAddressBookEntryScanQrButtonKey"),
onTap: () async {
@ -324,7 +432,6 @@ class _NewContactAddressEntryFormState
key: const Key("addAddressBookEntryViewAddressField"),
readOnly: false,
autocorrect: false,
enableSuggestions: false,
// inputFormatters: <TextInputFormatter>[
// FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")),
// ],

View file

@ -5,6 +5,7 @@ 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';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart';
@ -39,89 +40,92 @@ class _ChooseFromStackViewState extends ConsumerState<ChooseFromStackView> {
final walletIds = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getWalletIdsFor(coin: coin)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: const AppBarBackButton(),
title: Text(
"Choose your ${coin.ticker.toUpperCase()} wallet",
style: STextStyles.navBarTitle(context),
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: const AppBarBackButton(),
title: Text(
"Choose your ${coin.ticker.toUpperCase()} wallet",
style: STextStyles.navBarTitle(context),
),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: walletIds.isEmpty
? Column(
children: [
RoundedWhiteContainer(
child: Center(
child: Text(
"No ${coin.ticker.toUpperCase()} wallets",
style: STextStyles.itemSubtitle(context),
body: Padding(
padding: const EdgeInsets.all(16),
child: walletIds.isEmpty
? Column(
children: [
RoundedWhiteContainer(
child: Center(
child: Text(
"No ${coin.ticker.toUpperCase()} wallets",
style: STextStyles.itemSubtitle(context),
),
),
),
),
],
)
: ListView.builder(
itemCount: walletIds.length,
itemBuilder: (context, index) {
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletIds[index])));
],
)
: ListView.builder(
itemCount: walletIds.length,
itemBuilder: (context, index) {
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletIds[index])));
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: RawMaterialButton(
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: RawMaterialButton(
splashColor: Theme.of(context)
.extension<StackColors>()!
.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
padding: const EdgeInsets.all(0),
// color: Theme.of(context).extension<StackColors>()!.popupBG,
elevation: 0,
onPressed: () async {
if (mounted) {
Navigator.of(context).pop(manager.walletId);
}
},
child: RoundedWhiteContainer(
// color: Colors.transparent,
child: Row(
children: [
WalletInfoCoinIcon(coin: coin),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.walletName,
style: STextStyles.titleBold12(context),
overflow: TextOverflow.ellipsis,
),
const SizedBox(
height: 2,
),
WalletInfoRowBalanceFuture(
walletId: walletIds[index],
),
],
padding: const EdgeInsets.all(0),
// color: Theme.of(context).extension<StackColors>()!.popupBG,
elevation: 0,
onPressed: () async {
if (mounted) {
Navigator.of(context).pop(manager.walletId);
}
},
child: RoundedWhiteContainer(
// color: Colors.transparent,
child: Row(
children: [
WalletInfoCoinIcon(coin: coin),
const SizedBox(
width: 12,
),
)
],
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.walletName,
style: STextStyles.titleBold12(context),
overflow: TextOverflow.ellipsis,
),
const SizedBox(
height: 2,
),
WalletInfoRowBalanceFuture(
walletId: walletIds[index],
),
],
),
)
],
),
),
),
),
);
},
),
);
},
),
),
),
);
}

View file

@ -7,14 +7,24 @@ import 'package:stackwallet/models/trade_wallet_lookup.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart';
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@ -27,6 +37,8 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget {
required this.walletId,
this.routeOnSuccessName = WalletView.routeName,
required this.trade,
this.shouldSendPublicFiroFunds,
this.fromDesktopStep4 = false,
}) : super(key: key);
static const String routeName = "/confirmChangeNowSend";
@ -35,6 +47,8 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget {
final String walletId;
final String routeOnSuccessName;
final Trade trade;
final bool? shouldSendPublicFiroFunds;
final bool fromDesktopStep4;
@override
ConsumerState<ConfirmChangeNowSendView> createState() =>
@ -49,21 +63,31 @@ class _ConfirmChangeNowSendViewState
late final Trade trade;
Future<void> _attemptSend(BuildContext context) async {
unawaited(showDialog<void>(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
return const SendingTransactionDialog();
},
));
unawaited(
showDialog<void>(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
return const SendingTransactionDialog();
},
),
);
final String note = transactionInfo["note"] as String? ?? "";
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
try {
final txid = await manager.confirmSend(txData: transactionInfo);
late final String txid;
if (widget.shouldSendPublicFiroFunds == true) {
txid = await (manager.wallet as FiroWallet)
.confirmSendPublic(txData: transactionInfo);
} else {
txid = await manager.confirmSend(txData: transactionInfo);
}
unawaited(manager.refresh());
// save note
@ -82,6 +106,19 @@ class _ConfirmChangeNowSendViewState
// pop back to wallet
if (mounted) {
if (Util.isDesktop) {
Navigator.of(context, rootNavigator: true).pop();
// stupid hack
if (widget.fromDesktopStep4) {
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context, rootNavigator: true).pop();
}
}
Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName));
}
} catch (e) {
@ -118,6 +155,65 @@ class _ConfirmChangeNowSendViewState
}
}
Future<void> _confirmSend() async {
final dynamic unlocked;
final coin =
ref.read(walletsChangeNotifierProvider).getManager(walletId).coin;
if (Util.isDesktop) {
unlocked = await showDialog<bool?>(
context: context,
builder: (context) => DesktopDialog(
maxWidth: 580,
maxHeight: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const [
DesktopDialogCloseButton(),
],
),
Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: DesktopAuthSend(
coin: coin,
),
),
],
),
),
);
} else {
unlocked = await Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
builder: (_) => const LockscreenView(
showBackButton: true,
popOnSuccess: true,
routeOnSuccessArguments: true,
routeOnSuccess: "",
biometricsCancelButtonString: "CANCEL",
biometricsLocalizedReason: "Authenticate to send transaction",
biometricsAuthenticationTitle: "Confirm Transaction",
),
settings: const RouteSettings(name: "/confirmsendlockscreen"),
),
);
}
if (unlocked is bool && unlocked && mounted) {
await _attemptSend(context);
}
}
@override
void initState() {
transactionInfo = widget.transactionInfo;
@ -131,280 +227,507 @@ class _ConfirmChangeNowSendViewState
Widget build(BuildContext context) {
final managerProvider = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManagerProvider(walletId)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
leading: AppBarBackButton(
onPressed: () async {
// if (FocusScope.of(context).hasFocus) {
// FocusScope.of(context).unfocus();
// await Future<void>.delayed(Duration(milliseconds: 50));
// }
Navigator.of(context).pop();
},
),
title: Text(
"Confirm transaction",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
leading: AppBarBackButton(
onPressed: () async {
// if (FocusScope.of(context).hasFocus) {
// FocusScope.of(context).unfocus();
// await Future<void>.delayed(Duration(milliseconds: 50));
// }
Navigator.of(context).pop();
},
),
title: Text(
"Confirm transaction",
style: STextStyles.navBarTitle(context),
),
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
);
},
child: ConditionalParent(
condition: isDesktop,
builder: (child) => DesktopDialog(
maxHeight: double.infinity,
maxWidth: 580,
child: Column(
children: [
Row(
children: [
const SizedBox(
width: 6,
),
const AppBarBackButton(
isCompact: true,
iconSize: 23,
),
const SizedBox(
width: 12,
),
Text(
"Confirm ${ref.watch(managerProvider.select((value) => value.coin)).ticker} transaction",
style: STextStyles.desktopH3(context),
)
],
),
Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
child: Column(
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: child,
),
const SizedBox(
height: 16,
),
Row(
children: [
Text(
"Send ${ref.watch(managerProvider.select((value) => value.coin)).ticker}",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Send from",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
ref
.watch(walletsChangeNotifierProvider)
.getManager(walletId)
.walletName,
style: STextStyles.itemSubtitle12(context),
),
],
),
),
const SizedBox(
height: 12,
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"${trade.exchangeName} address",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
"${transactionInfo["address"] ?? "ERROR"}",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Amount",
style: STextStyles.smallMed12(context),
),
Text(
"${Format.satoshiAmountToPrettyString(
transactionInfo["recipientAmt"] as int,
ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
)} ${ref.watch(
managerProvider
.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Transaction fee",
style: STextStyles.smallMed12(context),
),
Text(
"${Format.satoshiAmountToPrettyString(
transactionInfo["fee"] as int,
ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
)} ${ref.watch(
managerProvider
.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
transactionInfo["note"] as String? ?? "",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade ID",
style: STextStyles.smallMed12(context),
),
Text(
trade.tradeId,
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
],
),
),
const SizedBox(
height: 12,
),
RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarBackSuccess,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Total amount",
style:
STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
),
Text(
"${Format.satoshiAmountToPrettyString(
(transactionInfo["fee"] as int) +
(transactionInfo["recipientAmt"] as int),
ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
)} ${ref.watch(
managerProvider
.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
textAlign: TextAlign.right,
),
],
),
),
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () async {
final unlocked = await Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator.useMaterialPageRoute,
builder: (_) => const LockscreenView(
showBackButton: true,
popOnSuccess: true,
routeOnSuccessArguments: true,
routeOnSuccess: "",
biometricsCancelButtonString: "CANCEL",
biometricsLocalizedReason:
"Authenticate to send transaction",
biometricsAuthenticationTitle:
"Confirm Transaction",
),
settings: const RouteSettings(
name: "/confirmsendlockscreen"),
),
);
if (unlocked is bool && unlocked && mounted) {
await _attemptSend(context);
}
},
child: Text(
"Send",
style: STextStyles.button(context),
),
"Transaction fee",
style:
STextStyles.desktopTextExtraExtraSmall(context),
),
],
),
const SizedBox(
height: 10,
),
RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
"${Format.satoshiAmountToPrettyString(
(transactionInfo["fee"] as int),
ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
ref.watch(
managerProvider.select((value) => value.coin),
),
)} ${ref.watch(
managerProvider.select((value) => value.coin),
).ticker}",
style:
STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
],
),
),
const SizedBox(
height: 16,
),
RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarBackSuccess,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Total amount",
style: STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
),
Text(
"${Format.satoshiAmountToPrettyString(
(transactionInfo["fee"] as int) +
(transactionInfo["recipientAmt"] as int),
ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
ref.watch(
managerProvider.select((value) => value.coin),
),
)} ${ref.watch(
managerProvider.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
textAlign: TextAlign.right,
),
],
),
),
const SizedBox(
height: 16,
),
Row(
children: [
Expanded(
child: SecondaryButton(
label: "Cancel",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Send",
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: _confirmSend,
),
),
],
)
],
),
),
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ConditionalParent(
condition: isDesktop,
builder: (child) => Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.background,
borderRadius: BorderRadius.vertical(
top: Radius.circular(
Constants.size.circularBorderRadius,
),
),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
child,
],
),
),
),
child: Text(
"Send ${ref.watch(managerProvider.select((value) => value.coin)).ticker}",
style: isDesktop
? STextStyles.desktopTextMedium(context)
: STextStyles.pageTitleH1(context),
),
),
);
},
isDesktop
? Container(
color:
Theme.of(context).extension<StackColors>()!.background,
height: 1,
)
: const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Send from",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
ref
.watch(walletsChangeNotifierProvider)
.getManager(walletId)
.walletName,
style: STextStyles.itemSubtitle12(context),
),
],
),
),
isDesktop
? Container(
color:
Theme.of(context).extension<StackColors>()!.background,
height: 1,
)
: const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"${trade.exchangeName} address",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
"${transactionInfo["address"] ?? "ERROR"}",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
isDesktop
? Container(
color:
Theme.of(context).extension<StackColors>()!.background,
height: 1,
)
: const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Amount",
style: STextStyles.smallMed12(context),
),
ConditionalParent(
condition: isDesktop,
builder: (child) => Row(
children: [
child,
Builder(builder: (context) {
final coin = ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManager(walletId).coin));
final price = ref.watch(
priceAnd24hChangeNotifierProvider
.select((value) => value.getPrice(coin)));
final amount = Format.satoshisToAmount(
transactionInfo["recipientAmt"] as int,
coin: coin,
);
final value = price.item1 * amount;
final currency = ref.watch(prefsChangeNotifierProvider
.select((value) => value.currency));
return Text(
" | ${value.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} $currency",
style:
STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle2,
),
);
})
],
),
child: Text(
"${Format.satoshiAmountToPrettyString(transactionInfo["recipientAmt"] as int, ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
), ref.watch(
managerProvider.select((value) => value.coin),
))} ${ref.watch(
managerProvider.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
),
],
),
),
isDesktop
? Container(
color:
Theme.of(context).extension<StackColors>()!.background,
height: 1,
)
: const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Transaction fee",
style: STextStyles.smallMed12(context),
),
Text(
"${Format.satoshiAmountToPrettyString(transactionInfo["fee"] as int, ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
), ref.watch(
managerProvider.select((value) => value.coin),
))} ${ref.watch(
managerProvider.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
],
),
),
isDesktop
? Container(
color:
Theme.of(context).extension<StackColors>()!.background,
height: 1,
)
: const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
transactionInfo["note"] as String? ?? "",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
isDesktop
? Container(
color:
Theme.of(context).extension<StackColors>()!.background,
height: 1,
)
: const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade ID",
style: STextStyles.smallMed12(context),
),
Text(
trade.tradeId,
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
],
),
),
if (!isDesktop)
const SizedBox(
height: 12,
),
if (!isDesktop)
RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarBackSuccess,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Total amount",
style: STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
),
Text(
"${Format.satoshiAmountToPrettyString((transactionInfo["fee"] as int) + (transactionInfo["recipientAmt"] as int), ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
), ref.watch(
managerProvider.select((value) => value.coin),
))} ${ref.watch(
managerProvider.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
textAlign: TextAlign.right,
),
],
),
),
if (!isDesktop)
const SizedBox(
height: 16,
),
if (!isDesktop) const Spacer(),
if (!isDesktop)
PrimaryButton(
label: "Send",
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: _confirmSend,
),
],
),
),
);
}

View file

@ -4,6 +4,8 @@ import 'package:stackwallet/providers/exchange/trade_note_service_provider.dart'
import 'package:stackwallet/utilities/constants.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/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
@ -45,103 +47,109 @@ class _EditNoteViewState extends ConsumerState<EditTradeNoteView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
appBar: AppBar(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Edit trade note",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Edit trade note",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(12),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
controller: _noteController,
style: STextStyles.field(context),
focusNode: noteFieldFocusNode,
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Note",
noteFieldFocusNode,
context,
).copyWith(
suffixIcon: _noteController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_noteController.text = "";
});
},
),
],
body: Padding(
padding: const EdgeInsets.all(12),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _noteController,
style: STextStyles.field(context),
focusNode: noteFieldFocusNode,
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Note",
noteFieldFocusNode,
context,
).copyWith(
suffixIcon: _noteController.text.isNotEmpty
? Padding(
padding:
const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_noteController.text = "";
});
},
),
],
),
),
),
)
: null,
)
: null,
),
),
),
),
const Spacer(),
TextButton(
onPressed: () async {
await ref.read(tradeNoteServiceProvider).set(
tradeId: widget.tradeId,
note: _noteController.text,
);
if (mounted) {
Navigator.of(context).pop();
}
},
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Save",
style: STextStyles.button(context),
),
)
],
const Spacer(),
TextButton(
onPressed: () async {
await ref.read(tradeNoteServiceProvider).set(
tradeId: widget.tradeId,
note: _noteController.text,
);
if (mounted) {
Navigator.of(context).pop();
}
},
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Save",
style: STextStyles.button(context),
),
)
],
),
),
),
),
),
);
},
);
},
),
),
),
);

View file

@ -8,6 +8,9 @@ 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';
import 'package:stackwallet/utilities/util.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/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
@ -118,93 +121,109 @@ class _FixedRateMarketPairCoinSelectionViewState
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to exchange",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to exchange",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isDesktop)
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
),
),
const SizedBox(
height: 10,
),
Text(
"Popular coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Builder(builder: (context) {
),
const SizedBox(
height: 10,
),
Text(
"Popular coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: Builder(builder: (context) {
final items = _markets
.where((e) => Coin.values
.where((coin) =>
@ -217,6 +236,7 @@ class _FixedRateMarketPairCoinSelectionViewState
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: items.length,
itemBuilder: (builderContext, index) {
final String ticker =
@ -278,84 +298,85 @@ class _FixedRateMarketPairCoinSelectionViewState
),
);
}),
const SizedBox(
height: 20,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
itemCount: _markets.length,
itemBuilder: (builderContext, index) {
final String ticker =
isFrom ? _markets[index].from : _markets[index].to;
),
const SizedBox(
height: 20,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: _markets.length,
itemBuilder: (builderContext, index) {
final String ticker =
isFrom ? _markets[index].from : _markets[index].to;
final tuple = _imageUrlAndNameFor(ticker);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(ticker);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
final tuple = _imageUrlAndNameFor(ticker);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(ticker);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
tuple.item1,
width: 24,
height: 24,
child: SvgPicture.network(
tuple.item1,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
const SizedBox(
width: 10,
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tuple.item2,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tuple.item2,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
],
),
),
);
},
),
),
);
},
),
),
],
),
),
],
),
);
}

View file

@ -6,6 +6,9 @@ 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';
import 'package:stackwallet/utilities/util.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/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
@ -74,94 +77,112 @@ class _FloatingRateCurrencySelectionViewState
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to exchange",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to exchange",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
filter("");
},
),
],
),
),
)
: null,
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
filter("");
},
),
],
),
),
)
: null,
),
),
const SizedBox(
height: 10,
),
Text(
"Popular coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Builder(builder: (context) {
),
const SizedBox(
height: 10,
),
Text(
"Popular coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: Builder(builder: (context) {
final items = _currencies
.where((e) => Coin.values
.where((coin) =>
@ -173,6 +194,7 @@ class _FloatingRateCurrencySelectionViewState
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: items.length,
itemBuilder: (builderContext, index) {
return Padding(
@ -230,80 +252,81 @@ class _FloatingRateCurrencySelectionViewState
),
);
}),
const SizedBox(
height: 20,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
itemCount: _currencies.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(_currencies[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
),
const SizedBox(
height: 20,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: _currencies.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(_currencies[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
_currencies[index].image,
width: 24,
height: 24,
child: SvgPicture.network(
_currencies[index].image,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
const SizedBox(
width: 10,
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_currencies[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
_currencies[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_currencies[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
_currencies[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
],
),
),
);
},
),
),
);
},
),
),
],
),
),
],
),
);
}

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -39,166 +40,169 @@ class _Step1ViewState extends State<Step1View> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Exchange",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Exchange",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
final width = MediaQuery.of(context).size.width - 32;
return Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StepRow(
count: 4,
current: 0,
width: width,
),
const SizedBox(
height: 14,
),
Text(
"Confirm amount",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"Network fees and other exchange charges are included in the rate.",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 24,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"You send",
style: STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
Text(
"${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
],
body: LayoutBuilder(
builder: (context, constraints) {
final width = MediaQuery.of(context).size.width - 32;
return Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StepRow(
count: 4,
current: 0,
width: width,
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"You receive",
style: STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
Text(
"~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
],
const SizedBox(
height: 14,
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
model.rateType == ExchangeRateType.estimated
? "Estimated rate"
: "Fixed rate",
style:
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemLabel,
Text(
"Confirm amount",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"Network fees and other exchange charges are included in the rate.",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 24,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"You send",
style: STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
),
Text(
model.rateInfo,
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
],
Text(
"${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
],
),
),
),
const SizedBox(
height: 12,
),
const Spacer(),
TextButton(
onPressed: () {
Navigator.of(context).pushNamed(Step2View.routeName,
arguments: model);
},
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Next",
style: STextStyles.button(context),
const SizedBox(
height: 12,
),
),
],
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"You receive",
style: STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
Text(
"~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
model.rateType == ExchangeRateType.estimated
? "Estimated rate"
: "Fixed rate",
style: STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemLabel,
),
),
Text(
model.rateInfo,
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemText),
),
],
),
),
const SizedBox(
height: 12,
),
const Spacer(),
TextButton(
onPressed: () {
Navigator.of(context).pushNamed(
Step2View.routeName,
arguments: model);
},
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Next",
style: STextStyles.button(context),
),
),
],
),
),
),
),
),
),
);
},
);
},
),
),
);
}

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@ import 'package:stackwallet/utilities/assets.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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -50,290 +51,295 @@ class _Step3ViewState extends ConsumerState<Step3View> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Exchange",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Exchange",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
final width = MediaQuery.of(context).size.width - 32;
return Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StepRow(
count: 4,
current: 2,
width: width,
),
const SizedBox(
height: 14,
),
Text(
"Confirm exchange details",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 24,
),
RoundedWhiteContainer(
child: Row(
children: [
Text(
"You send",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Text(
"${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context),
)
],
body: LayoutBuilder(
builder: (context, constraints) {
final width = MediaQuery.of(context).size.width - 32;
return Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StepRow(
count: 4,
current: 2,
width: width,
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
children: [
Text(
"You receive",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Text(
"${model.receiveAmount.toString()} ${model.receiveTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context),
)
],
const SizedBox(
height: 14,
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
children: [
Text(
"Estimated rate",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Text(
model.rateInfo,
style: STextStyles.itemSubtitle12(context),
)
],
Text(
"Confirm exchange details",
style: STextStyles.pageTitleH1(context),
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Recipient ${model.receiveTicker.toUpperCase()} address",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 4,
),
Text(
model.recipientAddress!,
style: STextStyles.itemSubtitle12(context),
)
],
const SizedBox(
height: 24,
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Refund ${model.sendTicker.toUpperCase()} address",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 4,
),
Text(
model.refundAddress!,
style: STextStyles.itemSubtitle12(context),
)
],
RoundedWhiteContainer(
child: Row(
children: [
Text(
"You send",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Text(
"${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context),
)
],
),
),
),
const SizedBox(
height: 8,
),
const Spacer(),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Back",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
children: [
Text(
"You receive",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Text(
"${model.receiveAmount.toString()} ${model.receiveTicker.toUpperCase()}",
style: STextStyles.itemSubtitle12(context),
)
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
children: [
Text(
"Estimated rate",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Text(
model.rateInfo,
style: STextStyles.itemSubtitle12(context),
)
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Recipient ${model.receiveTicker.toUpperCase()} address",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 4,
),
Text(
model.recipientAddress!,
style: STextStyles.itemSubtitle12(context),
)
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Refund ${model.sendTicker.toUpperCase()} address",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 4,
),
Text(
model.refundAddress!,
style: STextStyles.itemSubtitle12(context),
)
],
),
),
const SizedBox(
height: 8,
),
const Spacer(),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Back",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
),
),
),
),
const SizedBox(
width: 16,
),
Expanded(
child: TextButton(
onPressed: () async {
unawaited(
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.overlay
.withOpacity(0.6),
child: const CustomLoadingOverlay(
message: "Creating a trade",
eventBus: null,
const SizedBox(
width: 16,
),
Expanded(
child: TextButton(
onPressed: () async {
unawaited(
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.overlay
.withOpacity(0.6),
child: const CustomLoadingOverlay(
message: "Creating a trade",
eventBus: null,
),
),
),
),
),
);
);
final ExchangeResponse<Trade> response =
await ref
.read(exchangeProvider)
.createTrade(
from: model.sendTicker,
to: model.receiveTicker,
fixedRate: model.rateType !=
ExchangeRateType.estimated,
amount: model.reversed
? model.receiveAmount
: model.sendAmount,
addressTo: model.recipientAddress!,
extraId: null,
addressRefund: model.refundAddress!,
refundExtraId: "",
rateId: model.rateId,
reversed: model.reversed,
);
final ExchangeResponse<Trade> response =
await ref
.read(exchangeProvider)
.createTrade(
from: model.sendTicker,
to: model.receiveTicker,
fixedRate: model.rateType !=
ExchangeRateType.estimated,
amount: model.reversed
? model.receiveAmount
: model.sendAmount,
addressTo:
model.recipientAddress!,
extraId: null,
addressRefund:
model.refundAddress!,
refundExtraId: "",
rateId: model.rateId,
reversed: model.reversed,
);
if (response.value == null) {
if (mounted) {
Navigator.of(context).pop();
}
unawaited(showDialog<void>(
context: context,
barrierDismissible: true,
builder: (_) => StackDialog(
title: "Failed to create trade",
message:
response.exception?.toString(),
),
));
return;
}
// save trade to hive
await ref.read(tradesServiceProvider).add(
trade: response.value!,
shouldNotifyListeners: true,
);
String status = response.value!.status;
model.trade = response.value!;
// extra info if status is waiting
if (status == "Waiting") {
status += " for deposit";
}
if (response.value == null) {
if (mounted) {
Navigator.of(context).pop();
}
unawaited(showDialog<void>(
context: context,
barrierDismissible: true,
builder: (_) => StackDialog(
title: "Failed to create trade",
message: response.exception?.toString(),
),
unawaited(NotificationApi.showNotification(
changeNowId: model.trade!.tradeId,
title: status,
body: "Trade ID ${model.trade!.tradeId}",
walletId: "",
iconAssetName: Assets.svg.arrowRotate,
date: model.trade!.timestamp,
shouldWatchForUpdates: true,
coinName: "coinName",
));
return;
}
// save trade to hive
await ref.read(tradesServiceProvider).add(
trade: response.value!,
shouldNotifyListeners: true,
);
String status = response.value!.status;
model.trade = response.value!;
// extra info if status is waiting
if (status == "Waiting") {
status += " for deposit";
}
if (mounted) {
Navigator.of(context).pop();
}
unawaited(NotificationApi.showNotification(
changeNowId: model.trade!.tradeId,
title: status,
body: "Trade ID ${model.trade!.tradeId}",
walletId: "",
iconAssetName: Assets.svg.arrowRotate,
date: model.trade!.timestamp,
shouldWatchForUpdates: true,
coinName: "coinName",
));
if (mounted) {
unawaited(Navigator.of(context).pushNamed(
Step4View.routeName,
arguments: model,
));
}
},
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Next",
style: STextStyles.button(context),
if (mounted) {
unawaited(Navigator.of(context).pushNamed(
Step4View.routeName,
arguments: model,
));
}
},
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Next",
style: STextStyles.button(context),
),
),
),
),
],
),
],
],
),
],
),
),
),
),
),
),
);
},
);
},
),
),
);
}

File diff suppressed because it is too large Load diff

View file

@ -43,7 +43,11 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
),
child: ExchangeForm(),
),
),

View file

@ -8,8 +8,11 @@ import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/confirm_change_now_send.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -17,8 +20,14 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/format.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/animated_text.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/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/expandable.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@ -29,6 +38,8 @@ class SendFromView extends ConsumerStatefulWidget {
required this.trade,
required this.amount,
required this.address,
this.shouldPopRoot = false,
this.fromDesktopStep4 = false,
}) : super(key: key);
static const String routeName = "/sendFrom";
@ -37,6 +48,8 @@ class SendFromView extends ConsumerStatefulWidget {
final Decimal amount;
final String address;
final Trade trade;
final bool shouldPopRoot;
final bool fromDesktopStep4;
@override
ConsumerState<SendFromView> createState() => _SendFromViewState();
@ -49,24 +62,7 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
late final Trade trade;
String formatAmount(Decimal amount, Coin coin) {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoincash:
case Coin.dogecoin:
case Coin.epicCash:
case Coin.firo:
case Coin.namecoin:
case Coin.particl:
case Coin.bitcoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.dogecoinTestNet:
case Coin.firoTestNet:
return amount.toStringAsFixed(Constants.decimalPlaces);
case Coin.monero:
return amount.toStringAsFixed(Constants.decimalPlacesMonero);
case Coin.wownero:
return amount.toStringAsFixed(Constants.decimalPlacesWownero);
}
return amount.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
}
@override
@ -85,21 +81,70 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
final walletIds = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getWalletIdsFor(coin: coin)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Send from",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: child,
),
),
);
},
child: ConditionalParent(
condition: isDesktop,
builder: (child) => DesktopDialog(
maxHeight: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"Send from Stack",
style: STextStyles.desktopH3(context),
),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: widget.shouldPopRoot,
).pop,
),
],
),
Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: child,
),
],
),
),
title: Text(
"Send from",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
@ -107,15 +152,23 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
children: [
Text(
"You need to send ${formatAmount(amount, coin)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context)
: STextStyles.itemSubtitle(context),
),
],
),
const SizedBox(
height: 16,
),
Expanded(
ConditionalParent(
condition: !isDesktop,
builder: (child) => Expanded(
child: child,
),
child: ListView.builder(
primary: isDesktop ? false : null,
shrinkWrap: isDesktop,
itemCount: walletIds.length,
itemBuilder: (context, index) {
return Padding(
@ -125,6 +178,7 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
amount: amount,
address: address,
trade: trade,
fromDesktopStep4: widget.fromDesktopStep4,
),
);
},
@ -144,12 +198,14 @@ class SendFromCard extends ConsumerStatefulWidget {
required this.amount,
required this.address,
required this.trade,
this.fromDesktopStep4 = false,
}) : super(key: key);
final String walletId;
final Decimal amount;
final String address;
final Trade trade;
final bool fromDesktopStep4;
@override
ConsumerState<SendFromCard> createState() => _SendFromCardState();
@ -161,6 +217,147 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
late final String address;
late final Trade trade;
Future<void> _send(Manager manager, {bool? shouldSendPublicFiroFunds}) async {
final _amount = Format.decimalAmountToSatoshis(amount, manager.coin);
try {
bool wasCancelled = false;
unawaited(
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
return ConditionalParent(
condition: Util.isDesktop,
builder: (child) => DesktopDialog(
maxWidth: 400,
maxHeight: double.infinity,
child: Padding(
padding: const EdgeInsets.all(32),
child: child,
),
),
child: BuildingTransactionDialog(
onCancel: () {
wasCancelled = true;
Navigator.of(context).pop();
},
),
);
},
),
);
late Map<String, dynamic> txData;
// if not firo then do normal send
if (shouldSendPublicFiroFunds == null) {
txData = await manager.prepareSend(
address: address,
satoshiAmount: _amount,
args: {
"feeRate": FeeRateType.average,
// ref.read(feeRateTypeStateProvider)
},
);
} else {
final firoWallet = manager.wallet as FiroWallet;
// otherwise do firo send based on balance selected
if (shouldSendPublicFiroFunds) {
txData = await firoWallet.prepareSendPublic(
address: address,
satoshiAmount: _amount,
args: {
"feeRate": FeeRateType.average,
// ref.read(feeRateTypeStateProvider)
},
);
} else {
txData = await firoWallet.prepareSend(
address: address,
satoshiAmount: _amount,
args: {
"feeRate": FeeRateType.average,
// ref.read(feeRateTypeStateProvider)
},
);
}
}
if (!wasCancelled) {
// pop building dialog
if (mounted) {
Navigator.of(
context,
rootNavigator: Util.isDesktop,
).pop();
}
txData["note"] =
"${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange";
txData["address"] = address;
if (mounted) {
await Navigator.of(context).push(
RouteGenerator.getRoute(
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
builder: (_) => ConfirmChangeNowSendView(
transactionInfo: txData,
walletId: walletId,
routeOnSuccessName: Util.isDesktop
? DesktopExchangeView.routeName
: HomeView.routeName,
trade: trade,
shouldSendPublicFiroFunds: shouldSendPublicFiroFunds,
fromDesktopStep4: widget.fromDesktopStep4,
),
settings: const RouteSettings(
name: ConfirmChangeNowSendView.routeName,
),
),
);
}
}
} catch (e) {
// if (mounted) {
// pop building dialog
Navigator.of(context).pop();
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return StackDialog(
title: "Transaction failed",
message: e.toString(),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Ok",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
),
onPressed: () {
Navigator.of(context).pop();
},
),
);
},
);
// }
}
}
@override
void initState() {
walletId = widget.walletId;
@ -181,181 +378,295 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
final coin = manager.coin;
final isFiro = coin == Coin.firoTestNet || coin == Coin.firo;
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: MaterialButton(
splashColor: Theme.of(context).extension<StackColors>()!.highlight,
key: Key("walletsSheetItemButtonKey_$walletId"),
padding: const EdgeInsets.all(8),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
child: ConditionalParent(
condition: isFiro,
builder: (child) => Expandable(
header: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(12),
child: child,
),
),
body: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MaterialButton(
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
key: Key("walletsSheetItemButtonFiroPrivateKey_$walletId"),
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () async {
if (mounted) {
unawaited(
_send(
manager,
shouldSendPublicFiroFunds: false,
),
);
}
},
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.only(
top: 6,
left: 16,
right: 16,
bottom: 6,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Use private balance",
style: STextStyles.itemSubtitle(context),
),
FutureBuilder(
future: (manager.wallet as FiroWallet)
.availablePrivateBalance(),
builder: (builderContext,
AsyncSnapshot<Decimal> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
return Text(
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces:
Constants.decimalPlacesForCoin(coin),
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance..."
],
style: STextStyles.itemSubtitle(context),
);
}
},
),
],
),
SvgPicture.asset(
Assets.svg.chevronRight,
height: 14,
width: 7,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemLabel,
),
],
),
),
),
),
MaterialButton(
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
key: Key("walletsSheetItemButtonFiroPublicKey_$walletId"),
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () async {
if (mounted) {
unawaited(
_send(
manager,
shouldSendPublicFiroFunds: true,
),
);
}
},
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.only(
top: 6,
left: 16,
right: 16,
bottom: 6,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Use public balance",
style: STextStyles.itemSubtitle(context),
),
FutureBuilder(
future: (manager.wallet as FiroWallet)
.availablePublicBalance(),
builder: (builderContext,
AsyncSnapshot<Decimal> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
return Text(
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces:
Constants.decimalPlacesForCoin(coin),
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance..."
],
style: STextStyles.itemSubtitle(context),
);
}
},
),
],
),
SvgPicture.asset(
Assets.svg.chevronRight,
height: 14,
width: 7,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemLabel,
),
],
),
),
),
),
const SizedBox(
height: 6,
),
],
),
),
onPressed: () async {
final _amount = Format.decimalAmountToSatoshis(amount);
try {
bool wasCancelled = false;
unawaited(showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
return BuildingTransactionDialog(
onCancel: () {
wasCancelled = true;
Navigator.of(context).pop();
},
child: ConditionalParent(
condition: !isFiro,
builder: (child) => MaterialButton(
splashColor: Theme.of(context).extension<StackColors>()!.highlight,
key: Key("walletsSheetItemButtonKey_$walletId"),
padding: const EdgeInsets.all(8),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () async {
if (mounted) {
unawaited(
_send(manager),
);
},
));
final txData = await manager.prepareSend(
address: address,
satoshiAmount: _amount,
args: {
"feeRate": FeeRateType.average,
// ref.read(feeRateTypeStateProvider)
},
);
if (!wasCancelled) {
// pop building dialog
if (mounted) {
Navigator.of(context).pop();
}
txData["note"] =
"${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange";
txData["address"] = address;
if (mounted) {
await Navigator.of(context).push(
RouteGenerator.getRoute(
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
builder: (_) => ConfirmChangeNowSendView(
transactionInfo: txData,
walletId: walletId,
routeOnSuccessName: HomeView.routeName,
trade: trade,
),
settings: const RouteSettings(
name: ConfirmChangeNowSendView.routeName,
),
},
child: child,
),
child: Row(
children: [
Container(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.colorForCoin(manager.coin)
.withOpacity(0.5),
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
);
}
}
} catch (e) {
// if (mounted) {
// pop building dialog
Navigator.of(context).pop();
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return StackDialog(
title: "Transaction failed",
message: e.toString(),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Ok",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
child: Padding(
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
width: 24,
height: 24,
),
),
),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.walletName,
style: STextStyles.titleBold12(context),
),
if (!isFiro)
const SizedBox(
height: 2,
),
),
onPressed: () {
Navigator.of(context).pop();
},
),
);
},
);
// }
}
},
child: Row(
children: [
Container(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.colorForCoin(manager.coin)
.withOpacity(0.5),
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
if (!isFiro)
FutureBuilder(
future: manager.totalBalance,
builder:
(builderContext, AsyncSnapshot<Decimal> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
return Text(
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces:
Constants.decimalPlacesForCoin(coin),
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance..."
],
style: STextStyles.itemSubtitle(context),
);
}
},
),
],
),
),
child: Padding(
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
width: 24,
height: 24,
),
),
),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.walletName,
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 2,
),
FutureBuilder(
future: manager.totalBalance,
builder: (builderContext, AsyncSnapshot<Decimal> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return Text(
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces: coin == Coin.monero
? Constants.decimalPlacesMonero
: coin == Coin.wownero
? Constants.decimalPlacesWownero
: Constants.decimalPlaces,
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance..."
],
style: STextStyles.itemSubtitle(context),
);
}
},
),
],
),
),
],
],
),
),
),
);

View file

@ -15,7 +15,9 @@ import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/logger.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/animated_text.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class ExchangeProviderOptions extends ConsumerWidget {
@ -38,353 +40,414 @@ class ExchangeProviderOptions extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isDesktop = Util.isDesktop;
return RoundedWhiteContainer(
padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12),
borderColor: isDesktop
? Theme.of(context).extension<StackColors>()!.background
: null,
child: Column(
children: [
GestureDetector(
onTap: () {
if (ref.read(currentExchangeNameStateProvider.state).state !=
ChangeNowExchange.exchangeName) {
ref.read(currentExchangeNameStateProvider.state).state =
ChangeNowExchange.exchangeName;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(
ref.read(currentExchangeNameStateProvider.state).state);
}
},
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: ChangeNowExchange.exchangeName,
groupValue: ref
.watch(currentExchangeNameStateProvider.state)
.state,
onChanged: (value) {
if (value is String) {
ref
.read(currentExchangeNameStateProvider.state)
.state = value;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
ConditionalParent(
condition: isDesktop,
builder: (child) => MouseRegion(
cursor: SystemMouseCursors.click,
child: child,
),
child: GestureDetector(
onTap: () {
if (ref.read(currentExchangeNameStateProvider.state).state !=
ChangeNowExchange.exchangeName) {
ref.read(currentExchangeNameStateProvider.state).state =
ChangeNowExchange.exchangeName;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
.read(currentExchangeNameStateProvider.state)
.state);
}
},
child: Container(
color: Colors.transparent,
child: Padding(
padding: isDesktop
? const EdgeInsets.all(16)
: const EdgeInsets.all(0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: ChangeNowExchange.exchangeName,
groupValue: ref
.watch(currentExchangeNameStateProvider.state)
.state,
onChanged: (value) {
if (value is String) {
ref
.read(currentExchangeNameStateProvider.state)
.state);
}
},
),
),
const SizedBox(
width: 14,
),
SvgPicture.asset(
Assets.exchange.changeNow,
width: 24,
height: 24,
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
ChangeNowExchange.exchangeName,
style: STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
.state = value;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
.read(currentExchangeNameStateProvider
.state)
.state);
}
},
),
if (from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero)
FutureBuilder(
future: ChangeNowExchange().getEstimate(
from!,
to!,
reversed ? toAmount! : fromAmount!,
fixedRate,
reversed,
),
const SizedBox(
width: 14,
),
SvgPicture.asset(
Assets.exchange.changeNow,
width: isDesktop ? 32 : 24,
height: isDesktop ? 32 : 24,
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
ChangeNowExchange.exchangeName,
style: STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
),
builder: (context,
AsyncSnapshot<ExchangeResponse<Estimate>>
snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
final estimate = snapshot.data?.value;
if (estimate != null) {
Decimal rate;
if (estimate.reversed) {
rate =
(toAmount! / estimate.estimatedAmount)
if (from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero)
FutureBuilder(
future: ChangeNowExchange().getEstimate(
from!,
to!,
reversed ? toAmount! : fromAmount!,
fixedRate,
reversed,
),
builder: (context,
AsyncSnapshot<ExchangeResponse<Estimate>>
snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
final estimate = snapshot.data?.value;
if (estimate != null) {
Decimal rate;
if (estimate.reversed) {
rate = (toAmount! /
estimate.estimatedAmount)
.toDecimal(
scaleOnInfinitePrecision: 12);
} else {
rate = (estimate.estimatedAmount /
fromAmount!)
.toDecimal(
scaleOnInfinitePrecision: 12);
}
Coin coin;
try {
coin =
coinFromTickerCaseInsensitive(to!);
} catch (_) {
coin = Coin.bitcoin;
}
return Text(
"1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
value: rate,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select(
(value) => value.locale),
),
decimalPlaces:
Constants.decimalPlacesForCoin(
coin),
)} ${to!.toUpperCase()}",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
} else {
Logging.instance.log(
"$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",
level: LogLevel.Warning,
);
return Text(
"Failed to fetch rate",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
} else {
rate =
(estimate.estimatedAmount / fromAmount!)
.toDecimal(
scaleOnInfinitePrecision: 12);
}
return Text(
"1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
value: rate,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
return AnimatedText(
stringsToLoopThrough: const [
"Loading",
"Loading.",
"Loading..",
"Loading...",
],
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
decimalPlaces: to!.toUpperCase() ==
Coin.monero.ticker.toUpperCase()
? Constants.decimalPlacesMonero
: Constants.decimalPlaces,
)} ${to!.toUpperCase()}",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
} else {
Logging.instance.log(
"$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",
level: LogLevel.Warning,
);
return Text(
"Failed to fetch rate",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading",
"Loading.",
"Loading..",
"Loading...",
],
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
},
),
if (!(from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero))
Text(
"n/a",
style: STextStyles.itemSubtitle12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
);
}
},
),
if (!(from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero))
Text(
"n/a",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
],
),
),
),
),
const SizedBox(
height: 16,
),
GestureDetector(
onTap: () {
if (ref.read(currentExchangeNameStateProvider.state).state !=
SimpleSwapExchange.exchangeName) {
ref.read(currentExchangeNameStateProvider.state).state =
SimpleSwapExchange.exchangeName;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(
ref.read(currentExchangeNameStateProvider.state).state);
}
},
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: SimpleSwapExchange.exchangeName,
groupValue: ref
.watch(currentExchangeNameStateProvider.state)
.state,
onChanged: (value) {
if (value is String) {
ref
.read(currentExchangeNameStateProvider.state)
.state = value;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
if (isDesktop)
Container(
height: 1,
color: Theme.of(context).extension<StackColors>()!.background,
),
if (!isDesktop)
const SizedBox(
height: 16,
),
ConditionalParent(
condition: isDesktop,
builder: (child) => MouseRegion(
cursor: SystemMouseCursors.click,
child: child,
),
child: GestureDetector(
onTap: () {
if (ref.read(currentExchangeNameStateProvider.state).state !=
SimpleSwapExchange.exchangeName) {
ref.read(currentExchangeNameStateProvider.state).state =
SimpleSwapExchange.exchangeName;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
.read(currentExchangeNameStateProvider.state)
.state);
}
},
child: Container(
color: Colors.transparent,
child: Padding(
padding: isDesktop
? const EdgeInsets.all(16)
: const EdgeInsets.all(0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: SimpleSwapExchange.exchangeName,
groupValue: ref
.watch(currentExchangeNameStateProvider.state)
.state,
onChanged: (value) {
if (value is String) {
ref
.read(currentExchangeNameStateProvider.state)
.state);
}
},
),
),
const SizedBox(
width: 14,
),
SvgPicture.asset(
Assets.exchange.simpleSwap,
width: 24,
height: 24,
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
SimpleSwapExchange.exchangeName,
style: STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
.state = value;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
.read(currentExchangeNameStateProvider
.state)
.state);
}
},
),
if (from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero)
FutureBuilder(
future: SimpleSwapExchange().getEstimate(
from!,
to!,
// reversed ? toAmount! : fromAmount!,
fromAmount!,
fixedRate,
// reversed,
false,
),
const SizedBox(
width: 14,
),
SvgPicture.asset(
Assets.exchange.simpleSwap,
width: isDesktop ? 32 : 24,
height: isDesktop ? 32 : 24,
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
SimpleSwapExchange.exchangeName,
style: STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
),
builder: (context,
AsyncSnapshot<ExchangeResponse<Estimate>>
snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
final estimate = snapshot.data?.value;
if (estimate != null) {
Decimal rate = (estimate.estimatedAmount /
fromAmount!)
.toDecimal(scaleOnInfinitePrecision: 12);
if (from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero)
FutureBuilder(
future: SimpleSwapExchange().getEstimate(
from!,
to!,
// reversed ? toAmount! : fromAmount!,
fromAmount!,
fixedRate,
// reversed,
false,
),
builder: (context,
AsyncSnapshot<ExchangeResponse<Estimate>>
snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
final estimate = snapshot.data?.value;
if (estimate != null) {
Decimal rate = (estimate.estimatedAmount /
fromAmount!)
.toDecimal(
scaleOnInfinitePrecision: 12);
return Text(
"1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
value: rate,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
Coin coin;
try {
coin =
coinFromTickerCaseInsensitive(to!);
} catch (_) {
coin = Coin.bitcoin;
}
return Text(
"1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
value: rate,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select(
(value) => value.locale),
),
decimalPlaces:
Constants.decimalPlacesForCoin(
coin),
)} ${to!.toUpperCase()}",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
} else {
Logging.instance.log(
"$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}",
level: LogLevel.Warning,
);
return Text(
"Failed to fetch rate",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading",
"Loading.",
"Loading..",
"Loading...",
],
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
decimalPlaces: to!.toUpperCase() ==
Coin.monero.ticker.toUpperCase()
? Constants.decimalPlacesMonero
: Constants.decimalPlaces,
)} ${to!.toUpperCase()}",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
} else {
Logging.instance.log(
"$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}",
level: LogLevel.Warning,
);
return Text(
"Failed to fetch rate",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading",
"Loading.",
"Loading..",
"Loading...",
],
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
},
),
// if (!(from != null &&
// to != null &&
// (reversed
// ? toAmount != null && toAmount! > Decimal.zero
// : fromAmount != null &&
// fromAmount! > Decimal.zero)))
if (!(from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero))
Text(
"n/a",
style: STextStyles.itemSubtitle12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
);
}
},
),
// if (!(from != null &&
// to != null &&
// (reversed
// ? toAmount != null && toAmount! > Decimal.zero
// : fromAmount != null &&
// fromAmount! > Decimal.zero)))
if (!(from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero))
Text(
"n/a",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
],
),
),
),
),

View file

@ -7,8 +7,9 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/assets.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';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class RateTypeToggle extends ConsumerWidget {
const RateTypeToggle({
@ -21,106 +22,177 @@ class RateTypeToggle extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
debugPrint("BUILD: $runtimeType");
final isDesktop = Util.isDesktop;
final estimated = ref.watch(prefsChangeNotifierProvider
.select((value) => value.exchangeRateType)) ==
ExchangeRateType.estimated;
return RoundedWhiteContainer(
return RoundedContainer(
padding: const EdgeInsets.all(0),
color: isDesktop
? Theme.of(context).extension<StackColors>()!.buttonBackSecondary
: Theme.of(context).extension<StackColors>()!.popupBG,
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () {
if (!estimated) {
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.estimated;
onChanged?.call(ExchangeRateType.estimated);
}
},
child: RoundedContainer(
color: estimated
? Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG
: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.svg.lock,
width: 12,
height: 14,
color: estimated
? Theme.of(context).extension<StackColors>()!.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
const SizedBox(
width: 5,
),
Text(
"Estimate rate",
style: STextStyles.smallMed12(context).copyWith(
color: estimated
? Theme.of(context)
.extension<StackColors>()!
.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
child: ConditionalParent(
condition: isDesktop,
builder: (child) => MouseRegion(
cursor: estimated
? SystemMouseCursors.basic
: SystemMouseCursors.click,
child: child,
),
child: GestureDetector(
onTap: () {
if (!estimated) {
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.estimated;
onChanged?.call(ExchangeRateType.estimated);
}
},
child: RoundedContainer(
padding: isDesktop
? const EdgeInsets.all(17)
: const EdgeInsets.all(12),
color: estimated
? Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG
: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.svg.lockOpen,
width: 12,
height: 14,
color: isDesktop
? estimated
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary
: estimated
? Theme.of(context)
.extension<StackColors>()!
.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
const SizedBox(
width: 5,
),
Text(
"Estimate rate",
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: estimated
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
)
: STextStyles.smallMed12(context).copyWith(
color: estimated
? Theme.of(context)
.extension<StackColors>()!
.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
if (estimated) {
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.fixed;
onChanged?.call(ExchangeRateType.fixed);
}
},
child: RoundedContainer(
color: !estimated
? Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG
: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.svg.lock,
width: 12,
height: 14,
color: !estimated
? Theme.of(context).extension<StackColors>()!.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
const SizedBox(
width: 5,
),
Text(
"Fixed rate",
style: STextStyles.smallMed12(context).copyWith(
color: !estimated
? Theme.of(context)
.extension<StackColors>()!
.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
child: ConditionalParent(
condition: isDesktop,
builder: (child) => MouseRegion(
cursor: !estimated
? SystemMouseCursors.basic
: SystemMouseCursors.click,
child: child,
),
child: GestureDetector(
onTap: () {
if (estimated) {
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.fixed;
onChanged?.call(ExchangeRateType.fixed);
}
},
child: RoundedContainer(
padding: isDesktop
? const EdgeInsets.all(17)
: const EdgeInsets.all(12),
color: !estimated
? Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG
: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.svg.lock,
width: 12,
height: 14,
color: isDesktop
? !estimated
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary
: !estimated
? Theme.of(context)
.extension<StackColors>()!
.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
const SizedBox(
width: 5,
),
Text(
"Fixed rate",
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: !estimated
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
)
: STextStyles.smallMed12(context).copyWith(
color: !estimated
? Theme.of(context)
.extension<StackColors>()!
.textDark
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
),
),

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart';
import 'package:stackwallet/utilities/enums/coin_enum.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/custom_buttons/app_bar_icon_button.dart';
class WalletInitiatedExchangeView extends ConsumerStatefulWidget {
@ -47,75 +48,77 @@ class _WalletInitiatedExchangeViewState
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Exchange",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Exchange",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (context, constraints) {
final width = MediaQuery.of(context).size.width - 32;
return Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StepRow(
count: 4,
current: 0,
width: width,
),
const SizedBox(
height: 14,
),
Text(
"Exchange amount",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"Network fees and other exchange charges are included in the rate.",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 24,
),
ExchangeForm(
walletId: walletId,
coin: coin,
),
],
body: LayoutBuilder(
builder: (context, constraints) {
final width = MediaQuery.of(context).size.width - 32;
return Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StepRow(
count: 4,
current: 0,
width: width,
),
const SizedBox(
height: 14,
),
Text(
"Exchange amount",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"Network fees and other exchange charges are included in the rate.",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 24,
),
ExchangeForm(
walletId: walletId,
coin: coin,
),
],
),
),
),
),
),
),
);
},
);
},
),
),
);
}

View file

@ -20,6 +20,7 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/logger.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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@ -141,129 +142,138 @@ class _HomeViewState extends ConsumerState<HomeView> {
debugPrint("BUILD: $runtimeType");
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
key: _key,
appBar: AppBar(
automaticallyImplyLeading: false,
title: Row(
children: [
GestureDetector(
onTap: _hiddenOptions,
child: SvgPicture.asset(
Assets.svg.stackIcon(context),
width: 24,
height: 24,
),
),
const SizedBox(
width: 16,
),
Text(
"My Stack",
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("walletsViewAlertsButton"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
ref.watch(notificationsProvider
.select((value) => value.hasUnreadNotifications))
? Assets.svg.bellNew(context)
: Assets.svg.bell,
width: 20,
height: 20,
color: ref.watch(notificationsProvider
.select((value) => value.hasUnreadNotifications))
? null
: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
child: Background(
child: Scaffold(
backgroundColor: Colors.transparent,
key: _key,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor:
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
title: Row(
children: [
GestureDetector(
onTap: _hiddenOptions,
child: SvgPicture.asset(
Assets.svg.stackIcon(context),
width: 24,
height: 24,
),
onPressed: () {
// reset unread state
ref.refresh(unreadNotificationsStateProvider);
Navigator.of(context)
.pushNamed(NotificationsView.routeName)
.then((_) {
final Set<int> unreadNotificationIds = ref
.read(unreadNotificationsStateProvider.state)
.state;
if (unreadNotificationIds.isEmpty) return;
List<Future<void>> futures = [];
for (int i = 0;
i < unreadNotificationIds.length - 1;
i++) {
futures.add(ref.read(notificationsProvider).markAsRead(
unreadNotificationIds.elementAt(i), false));
}
// wait for multiple to update if any
Future.wait(futures).then((_) {
// only notify listeners once
ref
.read(notificationsProvider)
.markAsRead(unreadNotificationIds.last, true);
});
});
},
),
),
const SizedBox(
width: 16,
),
Text(
"My Stack",
style: STextStyles.navBarTitle(context),
)
],
),
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("walletsViewSettingsButton"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.gear,
actions: [
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("walletsViewAlertsButton"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
width: 20,
height: 20,
.backgroundAppBar,
icon: SvgPicture.asset(
ref.watch(notificationsProvider
.select((value) => value.hasUnreadNotifications))
? Assets.svg.bellNew(context)
: Assets.svg.bell,
width: 20,
height: 20,
color: ref.watch(notificationsProvider
.select((value) => value.hasUnreadNotifications))
? null
: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: () {
// reset unread state
ref.refresh(unreadNotificationsStateProvider);
Navigator.of(context)
.pushNamed(NotificationsView.routeName)
.then((_) {
final Set<int> unreadNotificationIds = ref
.read(unreadNotificationsStateProvider.state)
.state;
if (unreadNotificationIds.isEmpty) return;
List<Future<void>> futures = [];
for (int i = 0;
i < unreadNotificationIds.length - 1;
i++) {
futures.add(ref
.read(notificationsProvider)
.markAsRead(
unreadNotificationIds.elementAt(i), false));
}
// wait for multiple to update if any
Future.wait(futures).then((_) {
// only notify listeners once
ref
.read(notificationsProvider)
.markAsRead(unreadNotificationIds.last, true);
});
});
},
),
onPressed: () {
debugPrint("main view settings tapped");
Navigator.of(context)
.pushNamed(GlobalSettingsView.routeName);
},
),
),
),
],
),
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Column(
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("walletsViewSettingsButton"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
icon: SvgPicture.asset(
Assets.svg.gear,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
width: 20,
height: 20,
),
onPressed: () {
debugPrint("main view settings tapped");
Navigator.of(context)
.pushNamed(GlobalSettingsView.routeName);
},
),
),
),
],
),
body: Column(
children: [
if (Constants.enableExchange)
Container(
decoration: BoxDecoration(
color:
Theme.of(context).extension<StackColors>()!.background,
color: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
boxShadow: [
Theme.of(context)
.extension<StackColors>()!

View file

@ -3,14 +3,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:tuple/tuple.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:stackwallet/utilities/prefs.dart';
class IntroView extends StatefulWidget {
const IntroView({Key? key}) : super(key: key);
@ -32,118 +31,120 @@ class _IntroViewState extends State<IntroView> {
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType ");
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
body: Center(
child: !isDesktop
? Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Spacer(
flex: 2,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 300,
),
child: Image(
image: AssetImage(
Assets.png.stack,
),
),
),
),
const Spacer(
flex: 1,
),
AppNameText(
isDesktop: isDesktop,
),
const SizedBox(
height: 8,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 48,
),
child: IntroAboutText(
isDesktop: isDesktop,
),
),
const Spacer(
flex: 4,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 32,
),
child: PrivacyAndTOSText(
isDesktop: isDesktop,
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
child: Row(
children: [
Expanded(
child: GetStartedButton(
isDesktop: isDesktop,
),
),
],
),
),
],
)
: SizedBox(
width: 350,
height: 540,
child: Column(
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
body: Center(
child: !isDesktop
? Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Spacer(
flex: 2,
),
SizedBox(
width: 130,
height: 130,
child: SvgPicture.asset(
Assets.svg.stackIcon(context),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 300,
),
child: Image(
image: AssetImage(
Assets.png.stack,
),
),
),
),
const Spacer(
flex: 42,
flex: 1,
),
AppNameText(
isDesktop: isDesktop,
),
const Spacer(
flex: 24,
const SizedBox(
height: 8,
),
IntroAboutText(
isDesktop: isDesktop,
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 48,
),
child: IntroAboutText(
isDesktop: isDesktop,
),
),
const Spacer(
flex: 42,
flex: 4,
),
GetStartedButton(
isDesktop: isDesktop,
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 32,
),
child: PrivacyAndTOSText(
isDesktop: isDesktop,
),
),
const Spacer(
flex: 65,
),
PrivacyAndTOSText(
isDesktop: isDesktop,
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
child: Row(
children: [
Expanded(
child: GetStartedButton(
isDesktop: isDesktop,
),
),
],
),
),
],
)
: SizedBox(
width: 350,
height: 540,
child: Column(
children: [
const Spacer(
flex: 2,
),
SizedBox(
width: 130,
height: 130,
child: SvgPicture.asset(
Assets.svg.stackIcon(context),
),
),
const Spacer(
flex: 42,
),
AppNameText(
isDesktop: isDesktop,
),
const Spacer(
flex: 24,
),
IntroAboutText(
isDesktop: isDesktop,
),
const Spacer(
flex: 42,
),
GetStartedButton(
isDesktop: isDesktop,
),
const Spacer(
flex: 65,
),
PrivacyAndTOSText(
isDesktop: isDesktop,
),
],
),
),
),
),
),
);
}

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/background.dart';
class LoadingView extends StatelessWidget {
const LoadingView({Key? key}) : super(key: key);
@ -11,25 +12,27 @@ class LoadingView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Center(
child: SizedBox(
width: min(size.width, size.height) * 0.5,
child: Lottie.asset(
Assets.lottie.test2,
animate: true,
repeat: true,
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Center(
child: SizedBox(
width: min(size.width, size.height) * 0.5,
child: Lottie.asset(
Assets.lottie.test2,
animate: true,
repeat: true,
),
),
// child: Image(
// image: AssetImage(
// Assets.png.splash,
// ),
// width: MediaQuery.of(context).size.width * 0.5,
// ),
),
// child: Image(
// image: AssetImage(
// Assets.png.splash,
// ),
// width: MediaQuery.of(context).size.width * 0.5,
// ),
),
),
);

View file

@ -5,6 +5,7 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/unread_notifications_provider.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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -43,66 +44,68 @@ class _NotificationsViewState extends ConsumerState<NotificationsView> {
.where((element) => element.walletId == widget.walletId)
.toList(growable: false);
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
title: Text(
"Notifications",
style: STextStyles.navBarTitle(context),
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
title: Text(
"Notifications",
style: STextStyles.navBarTitle(context),
),
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
),
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
),
body: Padding(
padding: const EdgeInsets.all(12),
child: notifications.isNotEmpty
? Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: notifications.length,
itemBuilder: (builderContext, index) {
final notification = notifications[index];
if (notification.read == false) {
ref
.read(unreadNotificationsStateProvider.state)
.state
.add(notification.id);
}
return Padding(
padding: const EdgeInsets.all(4),
child: NotificationCard(
notification: notifications[index],
),
);
},
body: Padding(
padding: const EdgeInsets.all(12),
child: notifications.isNotEmpty
? Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: notifications.length,
itemBuilder: (builderContext, index) {
final notification = notifications[index];
if (notification.read == false) {
ref
.read(unreadNotificationsStateProvider.state)
.state
.add(notification.id);
}
return Padding(
padding: const EdgeInsets.all(4),
child: NotificationCard(
notification: notifications[index],
),
);
},
),
),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(4),
child: RoundedWhiteContainer(
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"Notifications will appear here",
style: STextStyles.itemSubtitle(context),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(4),
child: RoundedWhiteContainer(
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"Notifications will appear here",
style: STextStyles.itemSubtitle(context),
),
),
),
),
),
)
],
),
)
],
),
),
),
);
}

View file

@ -2,10 +2,10 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/biometrics.dart';
import 'package:stackwallet/utilities/constants.dart';
@ -13,6 +13,7 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart';
@ -20,15 +21,11 @@ class CreatePinView extends ConsumerStatefulWidget {
const CreatePinView({
Key? key,
this.popOnSuccess = false,
this.secureStore = const SecureStorageWrapper(
FlutterSecureStorage(),
),
this.biometrics = const Biometrics(),
}) : super(key: key);
static const String routeName = "/createPin";
final FlutterSecureStorageInterface secureStore;
final Biometrics biometrics;
final bool popOnSuccess;
@ -58,12 +55,12 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
final TextEditingController _pinPutController2 = TextEditingController();
final FocusNode _pinPutFocusNode2 = FocusNode();
late FlutterSecureStorageInterface _secureStore;
late SecureStorageInterface _secureStore;
late Biometrics biometrics;
@override
initState() {
_secureStore = widget.secureStore;
_secureStore = ref.read(secureStoreProvider);
biometrics = widget.biometrics;
super.initState();
}
@ -80,215 +77,219 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 70));
}
if (mounted) {
Navigator.of(context).pop(widget.popOnSuccess);
}
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 70));
}
if (mounted) {
Navigator.of(context).pop(widget.popOnSuccess);
}
},
),
),
),
body: SafeArea(
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
// page 1
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Create a PIN",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"This PIN protects access to your wallet.",
style: STextStyles.subtitle(context),
),
const SizedBox(
height: 36,
),
CustomPinPut(
fieldsCount: Constants.pinLength,
eachFieldHeight: 12,
eachFieldWidth: 12,
textStyle: STextStyles.label(context).copyWith(
fontSize: 1,
body: SafeArea(
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
// page 1
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Create a PIN",
style: STextStyles.pageTitleH1(context),
),
focusNode: _pinPutFocusNode1,
controller: _pinPutController1,
useNativeKeyboard: false,
obscureText: "",
inputDecoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
fillColor:
Theme.of(context).extension<StackColors>()!.background,
counterText: "",
const SizedBox(
height: 8,
),
submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
border: Border.all(
width: 1,
Text(
"This PIN protects access to your wallet.",
style: STextStyles.subtitle(context),
),
const SizedBox(
height: 36,
),
CustomPinPut(
fieldsCount: Constants.pinLength,
eachFieldHeight: 12,
eachFieldWidth: 12,
textStyle: STextStyles.label(context).copyWith(
fontSize: 1,
),
focusNode: _pinPutFocusNode1,
controller: _pinPutController1,
useNativeKeyboard: false,
obscureText: "",
inputDecoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
fillColor: Theme.of(context)
.extension<StackColors>()!
.background,
counterText: "",
),
submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
border: Border.all(
width: 1,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
),
),
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration,
onSubmit: (String pin) {
if (pin.length == Constants.pinLength) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
}
},
),
],
),
// page 2
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Confirm PIN",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"This PIN protects access to your wallet.",
style: STextStyles.subtitle(context),
),
const SizedBox(
height: 36,
),
CustomPinPut(
fieldsCount: Constants.pinLength,
eachFieldHeight: 12,
eachFieldWidth: 12,
textStyle: STextStyles.infoSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle3,
fontSize: 1,
),
focusNode: _pinPutFocusNode2,
controller: _pinPutController2,
useNativeKeyboard: false,
obscureText: "",
inputDecoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
fillColor:
Theme.of(context).extension<StackColors>()!.background,
counterText: "",
),
submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
border: Border.all(
width: 1,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
),
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration,
onSubmit: (String pin) async {
// _onSubmitCount++;
// if (_onSubmitCount - _onSubmitFailCount > 1) return;
if (_pinPutController1.text == _pinPutController2.text) {
// ask if want to use biometrics
final bool useBiometrics = (Platform.isLinux)
? false
: await biometrics.authenticate(
cancelButtonText: "SKIP",
localizedReason:
"You can use your fingerprint to unlock the wallet and confirm transactions.",
title: "Enable fingerprint authentication",
);
//TODO investigate why this crashes IOS, maybe ios persists securestorage even after an uninstall?
// This should never fail as we are writing a new pin
// assert(
// (await _secureStore.read(key: "stack_pin")) == null);
// possible alternative to the above but it does not guarantee we aren't overwriting a pin
// if (!Platform.isLinux)
// assert((await _secureStore.read(key: "stack_pin")) ==
// null);
assert(ref.read(prefsChangeNotifierProvider).hasPin ==
false);
await _secureStore.write(key: "stack_pin", value: pin);
ref.read(prefsChangeNotifierProvider).useBiometrics =
useBiometrics;
ref.read(prefsChangeNotifierProvider).hasPin = true;
await Future<void>.delayed(
const Duration(milliseconds: 200));
if (mounted) {
if (!widget.popOnSuccess) {
Navigator.of(context).pushNamedAndRemoveUntil(
HomeView.routeName,
(route) => false,
);
} else {
Navigator.of(context).pop();
}
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration,
onSubmit: (String pin) {
if (pin.length == Constants.pinLength) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
}
} else {
// _onSubmitFailCount++;
_pageController.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
},
),
],
),
showFloatingFlushBar(
type: FlushBarType.warning,
message: "PIN codes do not match. Try again.",
context: context,
iconAsset: Assets.svg.alertCircle,
);
// page 2
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Confirm PIN",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"This PIN protects access to your wallet.",
style: STextStyles.subtitle(context),
),
const SizedBox(
height: 36,
),
CustomPinPut(
fieldsCount: Constants.pinLength,
eachFieldHeight: 12,
eachFieldWidth: 12,
textStyle: STextStyles.infoSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle3,
fontSize: 1,
),
focusNode: _pinPutFocusNode2,
controller: _pinPutController2,
useNativeKeyboard: false,
obscureText: "",
inputDecoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
fillColor: Theme.of(context)
.extension<StackColors>()!
.background,
counterText: "",
),
submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
border: Border.all(
width: 1,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
),
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration,
onSubmit: (String pin) async {
// _onSubmitCount++;
// if (_onSubmitCount - _onSubmitFailCount > 1) return;
_pinPutController1.text = '';
_pinPutController2.text = '';
}
},
),
],
),
],
if (_pinPutController1.text == _pinPutController2.text) {
// ask if want to use biometrics
final bool useBiometrics = (Platform.isLinux)
? false
: await biometrics.authenticate(
cancelButtonText: "SKIP",
localizedReason:
"You can use your fingerprint to unlock the wallet and confirm transactions.",
title: "Enable fingerprint authentication",
);
//TODO investigate why this crashes IOS, maybe ios persists securestorage even after an uninstall?
// This should never fail as we are writing a new pin
// assert(
// (await _secureStore.read(key: "stack_pin")) == null);
// possible alternative to the above but it does not guarantee we aren't overwriting a pin
// if (!Platform.isLinux)
// assert((await _secureStore.read(key: "stack_pin")) ==
// null);
assert(ref.read(prefsChangeNotifierProvider).hasPin ==
false);
await _secureStore.write(key: "stack_pin", value: pin);
ref.read(prefsChangeNotifierProvider).useBiometrics =
useBiometrics;
ref.read(prefsChangeNotifierProvider).hasPin = true;
await Future<void>.delayed(
const Duration(milliseconds: 200));
if (mounted) {
if (!widget.popOnSuccess) {
Navigator.of(context).pushNamedAndRemoveUntil(
HomeView.routeName,
(route) => false,
);
} else {
Navigator.of(context).pop();
}
}
} else {
// _onSubmitFailCount++;
_pageController.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
showFloatingFlushBar(
type: FlushBarType.warning,
message: "PIN codes do not match. Try again.",
context: context,
iconAsset: Assets.svg.alertCircle,
);
_pinPutController1.text = '';
_pinPutController2.text = '';
}
},
),
],
),
],
),
),
),
);

View file

@ -2,12 +2,12 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
// import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
// import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart';
import 'package:stackwallet/widgets/shake/shake.dart';
@ -33,9 +34,6 @@ class LockscreenView extends ConsumerStatefulWidget {
this.popOnSuccess = false,
this.isInitialAppLogin = false,
this.routeOnSuccessArguments,
this.secureStore = const SecureStorageWrapper(
FlutterSecureStorage(),
),
this.biometrics = const Biometrics(),
this.onSuccess,
}) : super(key: key);
@ -50,7 +48,6 @@ class LockscreenView extends ConsumerStatefulWidget {
final String biometricsAuthenticationTitle;
final String biometricsLocalizedReason;
final String biometricsCancelButtonString;
final FlutterSecureStorageInterface secureStore;
final Biometrics biometrics;
final VoidCallback? onSuccess;
@ -134,7 +131,7 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
void initState() {
_shakeController = ShakeController();
_secureStore = widget.secureStore;
_secureStore = ref.read(secureStoreProvider);
biometrics = widget.biometrics;
_attempts = 0;
_timeout = Duration.zero;
@ -162,176 +159,180 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
final _pinTextController = TextEditingController();
final FocusNode _pinFocusNode = FocusNode();
late FlutterSecureStorageInterface _secureStore;
late SecureStorageInterface _secureStore;
late Biometrics biometrics;
Scaffold get _body => Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: widget.showBackButton
? AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 70));
}
if (mounted) {
Navigator.of(context).pop();
}
},
)
: Container(),
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Shake(
animationDuration: const Duration(milliseconds: 700),
animationRange: 12,
controller: _shakeController,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"Enter PIN",
style: STextStyles.pageTitleH1(context),
Widget get _body => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: widget.showBackButton
? AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 70));
}
if (mounted) {
Navigator.of(context).pop();
}
},
)
: Container(),
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Shake(
animationDuration: const Duration(milliseconds: 700),
animationRange: 12,
controller: _shakeController,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"Enter PIN",
style: STextStyles.pageTitleH1(context),
),
),
),
const SizedBox(
height: 52,
),
CustomPinPut(
fieldsCount: Constants.pinLength,
eachFieldHeight: 12,
eachFieldWidth: 12,
textStyle: STextStyles.label(context).copyWith(
fontSize: 1,
const SizedBox(
height: 52,
),
focusNode: _pinFocusNode,
controller: _pinTextController,
useNativeKeyboard: false,
obscureText: "",
inputDecoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
fillColor: Theme.of(context)
.extension<StackColors>()!
.background,
counterText: "",
),
submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
border: Border.all(
width: 1,
CustomPinPut(
fieldsCount: Constants.pinLength,
eachFieldHeight: 12,
eachFieldWidth: 12,
textStyle: STextStyles.label(context).copyWith(
fontSize: 1,
),
focusNode: _pinFocusNode,
controller: _pinTextController,
useNativeKeyboard: false,
obscureText: "",
inputDecoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
fillColor: Theme.of(context)
.extension<StackColors>()!
.background,
counterText: "",
),
submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
border: Border.all(
width: 1,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
),
),
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration,
onSubmit: (String pin) async {
_attempts++;
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration,
onSubmit: (String pin) async {
_attempts++;
if (_attempts > maxAttemptsBeforeThrottling) {
_attemptLock = true;
switch (_attempts) {
case 4:
_timeout = const Duration(seconds: 30);
break;
if (_attempts > maxAttemptsBeforeThrottling) {
_attemptLock = true;
switch (_attempts) {
case 4:
_timeout = const Duration(seconds: 30);
break;
case 5:
_timeout = const Duration(seconds: 60);
break;
case 5:
_timeout = const Duration(seconds: 60);
break;
case 6:
_timeout = const Duration(minutes: 5);
break;
case 6:
_timeout = const Duration(minutes: 5);
break;
case 7:
_timeout = const Duration(minutes: 10);
break;
case 7:
_timeout = const Duration(minutes: 10);
break;
case 8:
_timeout = const Duration(minutes: 20);
break;
case 8:
_timeout = const Duration(minutes: 20);
break;
case 9:
_timeout = const Duration(minutes: 30);
break;
case 9:
_timeout = const Duration(minutes: 30);
break;
default:
_timeout = const Duration(minutes: 60);
default:
_timeout = const Duration(minutes: 60);
}
unawaited(
Future<void>.delayed(_timeout).then((_) {
_attemptLock = false;
_attempts = 0;
}));
}
unawaited(Future<void>.delayed(_timeout).then((_) {
_attemptLock = false;
_attempts = 0;
}));
}
if (_attemptLock) {
String prettyTime = "";
if (_timeout.inSeconds >= 60) {
prettyTime += "${_timeout.inMinutes} minutes";
} else {
prettyTime += "${_timeout.inSeconds} seconds";
}
if (_attemptLock) {
String prettyTime = "";
if (_timeout.inSeconds >= 60) {
prettyTime += "${_timeout.inMinutes} minutes";
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message:
"Incorrect PIN entered too many times. Please wait $prettyTime",
context: context,
iconAsset: Assets.svg.alertCircle,
));
await Future<void>.delayed(
const Duration(milliseconds: 100));
_pinTextController.text = '';
return;
}
final storedPin =
await _secureStore.read(key: 'stack_pin');
if (storedPin == pin) {
await Future<void>.delayed(
const Duration(milliseconds: 200));
unawaited(_onUnlock());
} else {
prettyTime += "${_timeout.inSeconds} seconds";
unawaited(_shakeController.shake());
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Incorrect PIN. Please try again",
context: context,
iconAsset: Assets.svg.alertCircle,
));
await Future<void>.delayed(
const Duration(milliseconds: 100));
_pinTextController.text = '';
}
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message:
"Incorrect PIN entered too many times. Please wait $prettyTime",
context: context,
iconAsset: Assets.svg.alertCircle,
));
await Future<void>.delayed(
const Duration(milliseconds: 100));
_pinTextController.text = '';
return;
}
final storedPin =
await _secureStore.read(key: 'stack_pin');
if (storedPin == pin) {
await Future<void>.delayed(
const Duration(milliseconds: 200));
unawaited(_onUnlock());
} else {
unawaited(_shakeController.shake());
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Incorrect PIN. Please try again",
context: context,
iconAsset: Assets.svg.alertCircle,
));
await Future<void>.delayed(
const Duration(milliseconds: 100));
_pinTextController.text = '';
}
},
),
],
},
),
],
),
),
),
),
],
],
),
),
),
);

View file

@ -1,9 +1,11 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
// import 'package:document_file_save_plus/document_file_save_plus.dart';
import 'package:decimal/decimal.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/svg.dart';
@ -11,6 +13,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
@ -19,7 +22,12 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/logger.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@ -50,6 +58,10 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
late TextEditingController amountController;
late TextEditingController noteController;
late final bool isDesktop;
late String _uriString;
bool didGenerate = false;
final _amountFocusNode = FocusNode();
final _noteFocusNode = FocusNode();
@ -62,26 +74,217 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData!.buffer.asUint8List();
// if (shouldSaveInsteadOfShare) {
// await DocumentFileSavePlus.saveFile(
// pngBytes,
// "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png",
// "image/png");
// } else {
final tempDir = await getTemporaryDirectory();
final file = await File("${tempDir.path}/qrcode.png").create();
await file.writeAsBytes(pngBytes);
if (shouldSaveInsteadOfShare) {
if (Util.isDesktop) {
final dir = Directory("${Platform.environment['HOME']}");
if (!dir.existsSync()) {
throw Exception(
"Home dir not found while trying to open filepicker on QR image save");
}
final path = await FilePicker.platform.saveFile(
fileName: "qrcode.png",
initialDirectory: dir.path,
);
await Share.shareFiles(["${tempDir.path}/qrcode.png"],
text: "Receive URI QR Code");
// }
if (path != null) {
final file = File(path);
if (file.existsSync()) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.warning,
message: "$path already exists!",
context: context,
),
);
} else {
await file.writeAsBytes(pngBytes);
unawaited(
showFloatingFlushBar(
type: FlushBarType.success,
message: "$path saved!",
context: context,
),
);
}
}
} else {
// await DocumentFileSavePlus.saveFile(
// pngBytes,
// "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png",
// "image/png");
}
} else {
final tempDir = await getTemporaryDirectory();
final file = await File("${tempDir.path}/qrcode.png").create();
await file.writeAsBytes(pngBytes);
await Share.shareFiles(["${tempDir.path}/qrcode.png"],
text: "Receive URI QR Code");
}
} catch (e) {
debugPrint(e.toString());
}
}
String? _generateURI() {
final amountString = amountController.text;
final noteString = noteController.text;
if (amountString.isNotEmpty && Decimal.tryParse(amountString) == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid amount",
context: context,
);
return null;
}
Map<String, String> queryParams = {};
if (amountString.isNotEmpty) {
queryParams["amount"] = amountString;
}
if (noteString.isNotEmpty) {
queryParams["message"] = noteString;
}
String receivingAddress = widget.receivingAddress;
if ((widget.coin == Coin.bitcoincash ||
widget.coin == Coin.bitcoincashTestnet) &&
receivingAddress.contains(":")) {
// remove cash addr prefix
receivingAddress = receivingAddress.split(":").sublist(1).join();
}
final uriString = AddressUtils.buildUriString(
widget.coin,
receivingAddress,
queryParams,
);
Logging.instance.log("Generated receiving QR code for: $uriString",
level: LogLevel.Info);
return uriString;
}
void onGeneratePressed() {
final uriString = _generateURI();
if (uriString == null) {
return;
}
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (_) {
final width = MediaQuery.of(context).size.width / 2;
return StackDialogBase(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"New QR code",
style: STextStyles.pageTitleH2(context),
),
),
const SizedBox(
height: 12,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: SizedBox(
width: width + 20,
height: width + 20,
child: QrImage(
data: uriString,
size: width,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
),
const SizedBox(
height: 12,
),
Center(
child: SizedBox(
width: width,
child: TextButton(
onPressed: () async {
// TODO: add save button as well
await _capturePng(true);
},
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: SvgPicture.asset(
Assets.svg.share,
width: 14,
height: 14,
),
),
const SizedBox(
width: 4,
),
Column(
children: [
Text(
"Share",
textAlign: TextAlign.center,
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
),
const SizedBox(
height: 2,
),
],
),
],
),
),
),
),
],
),
);
},
);
}
@override
void initState() {
isDesktop = Util.isDesktop;
String receivingAddress = widget.receivingAddress;
if ((widget.coin == Coin.bitcoincash ||
widget.coin == Coin.bitcoincashTestnet) &&
receivingAddress.contains(":")) {
// remove cash addr prefix
receivingAddress = receivingAddress.split(":").sublist(1).join();
}
_uriString = AddressUtils.buildUriString(
widget.coin,
receivingAddress,
{},
);
amountController = TextEditingController();
noteController = TextEditingController();
super.initState();
@ -100,311 +303,342 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 70));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Generate QR code",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (buildContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 70));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
title: Text(
"Generate QR code",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (buildContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
child: Text(
"The new QR code with your address, amount and note will appear in the pop up window.",
style: STextStyles.itemSubtitle(context),
),
),
const SizedBox(
height: 12,
),
Text(
"Amount (Optional)",
style: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
controller: amountController,
focusNode: _amountFocusNode,
style: STextStyles.field(context),
keyboardType: const TextInputType.numberWithOptions(
decimal: true),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Amount",
_amountFocusNode,
context,
).copyWith(
suffixIcon: amountController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
amountController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 12,
),
Text(
"Note (Optional)",
style: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
controller: noteController,
focusNode: _noteFocusNode,
style: STextStyles.field(context),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Note",
_noteFocusNode,
context,
).copyWith(
suffixIcon: noteController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
noteController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
// SizedBox()
// Spacer(),
const SizedBox(
height: 8,
),
TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () {
final amountString = amountController.text;
final noteString = noteController.text;
if (amountString.isNotEmpty &&
Decimal.tryParse(amountString) == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid amount",
context: context,
);
return;
}
String query = "";
if (amountString.isNotEmpty) {
query += "amount=$amountString";
}
if (noteString.isNotEmpty) {
if (query.isNotEmpty) {
query += "&";
}
query += "message=$noteString";
}
final uri = Uri(
scheme: widget.coin.uriScheme,
host: widget.receivingAddress,
query: query.isNotEmpty ? query : null,
);
final uriString =
uri.toString().replaceFirst("://", ":");
Logging.instance.log(
"Generated receiving QR code for: $uriString",
level: LogLevel.Info);
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (_) {
final width =
MediaQuery.of(context).size.width / 2;
return StackDialogBase(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"New QR code",
style:
STextStyles.pageTitleH2(context),
),
),
const SizedBox(
height: 12,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: SizedBox(
width: width + 20,
height: width + 20,
child: QrImage(
data: uriString,
size: width,
backgroundColor: Theme.of(
context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(
context)
.extension<StackColors>()!
.accentColorDark),
),
),
),
const SizedBox(
height: 12,
),
Center(
child: SizedBox(
width: width,
child: TextButton(
onPressed: () async {
// TODO: add save button as well
await _capturePng(true);
},
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(
context),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Center(
child: SvgPicture.asset(
Assets.svg.share,
width: 14,
height: 14,
),
),
const SizedBox(
width: 4,
),
Column(
children: [
Text(
"Share",
textAlign:
TextAlign.center,
style: STextStyles.button(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.buttonTextSecondary,
),
),
const SizedBox(
height: 2,
),
],
),
],
),
),
),
),
],
),
);
},
);
},
child: Text(
"Generate QR Code",
style: STextStyles.button(context),
),
),
],
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
),
child: Padding(
padding: isDesktop
? const EdgeInsets.only(
top: 12,
left: 32,
right: 32,
bottom: 32,
)
: const EdgeInsets.all(0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
RoundedWhiteContainer(
child: Text(
"The new QR code with your address, amount and note will appear in the pop up window.",
style: STextStyles.itemSubtitle(context),
),
),
if (!isDesktop)
const SizedBox(
height: 12,
),
Text(
"Amount (Optional)",
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
)
: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: isDesktop ? 10 : 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: amountController,
focusNode: _amountFocusNode,
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultText,
height: 1.8,
)
: STextStyles.field(context),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Amount",
_amountFocusNode,
context,
).copyWith(
contentPadding: isDesktop
? const EdgeInsets.only(
left: 16,
top: 11,
bottom: 12,
right: 5,
)
: null,
suffixIcon: amountController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
amountController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
);
},
SizedBox(
height: isDesktop ? 20 : 12,
),
Text(
"Note (Optional)",
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
)
: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: isDesktop ? 10 : 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: noteController,
focusNode: _noteFocusNode,
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultText,
height: 1.8,
)
: STextStyles.field(context),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Note",
_noteFocusNode,
context,
).copyWith(
contentPadding: isDesktop
? const EdgeInsets.only(
left: 16,
top: 11,
bottom: 12,
right: 5,
)
: null,
suffixIcon: noteController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
noteController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
SizedBox(
height: isDesktop ? 20 : 8,
),
PrimaryButton(
label: "Generate QR code",
onPressed: isDesktop
? () {
final uriString = _generateURI();
if (uriString == null) {
return;
}
setState(() {
didGenerate = true;
_uriString = uriString;
});
}
: onGeneratePressed,
buttonHeight: isDesktop ? ButtonHeight.l : null,
),
if (isDesktop && didGenerate)
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 20,
),
RoundedWhiteContainer(
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
width: isDesktop ? 370 : null,
child: Column(
children: [
Text(
"New QR Code",
style: STextStyles.desktopTextMedium(context),
),
const SizedBox(
height: 16,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: SizedBox(
width: 234,
height: 234,
child: QrImage(
data: _uriString,
size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
),
const SizedBox(
height: 12,
),
Row(
mainAxisAlignment: isDesktop
? MainAxisAlignment.center
: MainAxisAlignment.start,
children: [
if (!isDesktop)
SecondaryButton(
width: 170,
buttonHeight:
isDesktop ? ButtonHeight.l : null,
onPressed: () async {
await _capturePng(false);
},
label: "Share",
icon: SvgPicture.asset(
Assets.svg.share,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
),
if (!isDesktop)
const SizedBox(
width: 16,
),
PrimaryButton(
width: 170,
buttonHeight:
isDesktop ? ButtonHeight.l : null,
onPressed: () async {
// TODO: add save functionality instead of share
// save works on linux at the moment
await _capturePng(true);
},
label: "Save",
icon: SvgPicture.asset(
Assets.svg.arrowDown,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimary,
),
),
],
)
],
),
),
],
),
],
),
],
),
),
);
}

View file

@ -12,9 +12,9 @@ import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.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/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';
@ -115,147 +115,149 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
}
});
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Receive ${coin.ticker}",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Receive ${coin.ticker}",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(text: receivingAddress),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: RoundedWhiteContainer(
child: Column(
children: [
Row(
children: [
Text(
"Your ${coin.ticker} address",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Row(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 10,
height: 10,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
const SizedBox(
width: 4,
),
Text(
"Copy",
style: STextStyles.link2(context),
),
],
),
],
),
const SizedBox(
height: 4,
),
Row(
children: [
Expanded(
child: Text(
receivingAddress,
style: STextStyles.itemSubtitle12(context),
),
),
],
),
],
),
),
),
if (coin != Coin.epicCash)
const SizedBox(
height: 12,
),
if (coin != Coin.epicCash)
TextButton(
onPressed: generateNewAddress,
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Generate new address",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
const SizedBox(
height: 30,
),
RoundedWhiteContainer(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
body: Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(text: receivingAddress),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: RoundedWhiteContainer(
child: Column(
children: [
QrImage(
data: "${coin.uriScheme}:$receivingAddress",
size: MediaQuery.of(context).size.width / 2,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
const SizedBox(
height: 20,
Row(
children: [
Text(
"Your ${coin.ticker} address",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Row(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 10,
height: 10,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
const SizedBox(
width: 4,
),
Text(
"Copy",
style: STextStyles.link2(context),
),
],
),
],
),
BlueTextButton(
text: "Create new QR code",
onTap: () async {
unawaited(Navigator.of(context).push(
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator.useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView(
coin: coin,
receivingAddress: receivingAddress,
),
settings: const RouteSettings(
name: GenerateUriQrCodeView.routeName,
),
const SizedBox(
height: 4,
),
Row(
children: [
Expanded(
child: Text(
receivingAddress,
style: STextStyles.itemSubtitle12(context),
),
));
},
),
],
),
],
),
),
),
),
],
if (coin != Coin.epicCash)
const SizedBox(
height: 12,
),
if (coin != Coin.epicCash)
TextButton(
onPressed: generateNewAddress,
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Generate new address",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
const SizedBox(
height: 30,
),
RoundedWhiteContainer(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
children: [
QrImage(
data: "${coin.uriScheme}:$receivingAddress",
size: MediaQuery.of(context).size.width / 2,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
const SizedBox(
height: 20,
),
BlueTextButton(
text: "Create new QR code",
onTap: () async {
unawaited(Navigator.of(context).push(
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator.useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView(
coin: coin,
receivingAddress: receivingAddress,
),
settings: const RouteSettings(
name: GenerateUriQrCodeView.routeName,
),
),
));
},
),
],
),
),
),
),
],
),
),
),
),

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