Merge pull request #248 from cypherstack/staging

Staging
This commit is contained in:
Diego Salazar 2022-11-29 09:52:46 -07:00 committed by GitHub
commit 9875edd804
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
287 changed files with 32947 additions and 19153 deletions

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

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

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 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,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

@ -1 +1 @@
Subproject commit 2da77438527732dfaa5398aa391eab5253dabe19
Subproject commit de29931dacc9aefaf42a9ca139a8754a42adc40d

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';
@ -47,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';
@ -73,25 +76,32 @@ void main() async {
Util.libraryPath = await getLibraryDirectory();
}
if (Util.isDesktop) {
setWindowTitle('Stack Wallet');
setWindowMinSize(const Size(1200, 900));
setWindowMaxSize(Size.infinite);
Screen? screen;
if (Platform.isLinux || Util.isDesktop) {
screen = await getCurrentScreen();
Util.screenWidth = screen?.frame.width;
}
Directory appDirectory = (await getApplicationDocumentsDirectory());
if (Platform.isIOS) {
appDirectory = (await getLibraryDirectory());
}
if (Platform.isLinux || Logging.isArmLinux) {
appDirectory = Directory("${appDirectory.path}/.stackwallet");
await appDirectory.create();
if (Util.isDesktop) {
setWindowTitle('Stack Wallet');
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),
);
}
}
// 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);
@ -140,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);
}
}
}
@ -199,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;
@ -208,8 +229,29 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
late final Completer<void> loadingCompleter;
bool didLoad = false;
bool didLoadShared = false;
bool _desktopHasPassword = false;
Future<void> loadShared() async {
if (didLoadShared) {
return;
}
didLoadShared = true;
await DB.instance.init();
await ref.read(prefsChangeNotifierProvider).init();
final familiarity = ref.read(prefsChangeNotifierProvider).familiarity + 1;
ref.read(prefsChangeNotifierProvider).familiarity = familiarity;
Constants.exchangeForExperiencedUsers(familiarity);
if (Util.isDesktop) {
_desktopHasPassword =
await ref.read(storageCryptoHandlerProvider).hasPassword();
}
}
Future<void> load() async {
try {
if (didLoad) {
@ -217,19 +259,15 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
}
didLoad = true;
await DB.instance.init();
await _prefs.init();
if (Util.isDesktop) {
_desktopHasPassword =
await ref.read(storageCryptoHandlerProvider).hasPassword();
if (!Util.isDesktop) {
await loadShared();
}
_notificationsService = ref.read(notificationsProvider);
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
_tradesService = ref.read(tradesServiceProvider);
NotificationApi.prefs = _prefs;
NotificationApi.prefs = ref.read(prefsChangeNotifierProvider);
NotificationApi.notificationsService = _notificationsService;
unawaited(ref.read(baseCurrenciesProvider).update());
@ -238,23 +276,25 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
await _notificationsService.init(
nodeService: _nodeService,
tradesService: _tradesService,
prefs: _prefs,
prefs: ref.read(prefsChangeNotifierProvider),
);
ref.read(priceAnd24hChangeNotifierProvider).start(true);
await _wallets.load(_prefs);
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 &&
_prefs.externalCalls &&
await _prefs.isExternalCallsSet()) {
ref.read(prefsChangeNotifierProvider).externalCalls &&
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
}
if (_prefs.isAutoBackupEnabled) {
switch (_prefs.backupFrequencyType) {
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) {
switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) {
case BackupFrequencyType.everyTenMinutes:
ref.read(autoSWBServiceProvider).startPeriodicBackupTimer(
duration: const Duration(minutes: 10));
@ -278,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);
@ -294,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
@ -401,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) {
@ -547,51 +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 (Util.isDesktop &&
(_wallets.hasWallets || _desktopHasPassword)) {
String? startupWalletId;
if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
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;
}
return DesktopLoginView(startupWalletId: startupWalletId);
} else if (!Util.isDesktop &&
(_wallets.hasWallets || _prefs.hasPin)) {
// return HomeView();
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();
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 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

@ -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';
@ -182,39 +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

@ -32,6 +32,11 @@ class SearchableCoinList extends ConsumerWidget {
// 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(),
),
),
);
},
),
),
),
),
);
}

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

@ -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,

View file

@ -19,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';
@ -273,6 +274,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
widget.coin,
walletId,
widget.walletName,
ref.read(secureStoreProvider),
node,
txTracker,
ref.read(prefsChangeNotifierProvider),

View file

@ -13,22 +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';
import 'package:stackwallet/utilities/util.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();
@ -39,9 +44,6 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
final _searchFocusNode = FocusNode();
List<Contact>? _cache;
List<Contact>? _cacheFav;
String _searchTerm = "";
@override
@ -50,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;
@ -59,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);
@ -100,291 +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(
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,
),
),
),
),
);
},
),
),
);
},
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

@ -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,14 +9,18 @@ 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';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class EditContactNameEmojiView extends ConsumerStatefulWidget {
const EditContactNameEmojiView({
Key? key,
@ -69,268 +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(
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: 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,14 +16,13 @@ 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';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class NewContactAddressEntryForm extends ConsumerStatefulWidget {
const NewContactAddressEntryForm({
Key? key,
@ -48,6 +49,8 @@ class _NewContactAddressEntryFormState
late final FocusNode addressLabelFocusNode;
late final FocusNode addressFocusNode;
List<Coin> coins = [];
@override
void initState() {
addressLabelController = TextEditingController()
@ -56,6 +59,7 @@ class _NewContactAddressEntryFormState
..text = ref.read(addressEntryDataProvider(widget.id)).address ?? "";
addressLabelFocusNode = FocusNode();
addressFocusNode = FocusNode();
coins = [...Coin.values];
super.initState();
}
@ -70,86 +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(
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,
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,
),
@ -168,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),
@ -212,6 +311,7 @@ class _NewContactAddressEntryFormState
addressFocusNode,
context,
).copyWith(
labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
suffixIcon: UnconstrainedBox(
child: Row(
children: [
@ -251,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 {

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,15 +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';
@ -29,6 +38,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget {
this.routeOnSuccessName = WalletView.routeName,
required this.trade,
this.shouldSendPublicFiroFunds,
this.fromDesktopStep4 = false,
}) : super(key: key);
static const String routeName = "/confirmChangeNowSend";
@ -38,6 +48,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget {
final String routeOnSuccessName;
final Trade trade;
final bool? shouldSendPublicFiroFunds;
final bool fromDesktopStep4;
@override
ConsumerState<ConfirmChangeNowSendView> createState() =>
@ -52,14 +63,16 @@ 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 =
@ -93,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) {
@ -129,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;
@ -142,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,13 +4,13 @@ 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';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class EditTradeNoteView extends ConsumerStatefulWidget {
const EditTradeNoteView({
Key? key,
@ -47,105 +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(
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 = "";
});
},
),
],
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';
@ -16,8 +19,6 @@ import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:tuple/tuple.dart';
import 'package:stackwallet/utilities/util.dart';
class FixedRateMarketPairCoinSelectionView extends ConsumerStatefulWidget {
const FixedRateMarketPairCoinSelectionView({
Key? key,
@ -120,95 +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(
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,
),
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) =>
@ -221,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 =
@ -282,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';
@ -13,8 +16,6 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class FloatingRateCurrencySelectionView extends StatefulWidget {
const FloatingRateCurrencySelectionView({
Key? key,
@ -76,96 +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(
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,
),
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) =>
@ -177,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(
@ -234,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,6 +8,7 @@ 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';
@ -19,9 +20,13 @@ 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';
@ -33,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";
@ -41,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();
@ -53,25 +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.litecoin:
case Coin.dogecoin:
case Coin.epicCash:
case Coin.firo:
case Coin.namecoin:
case Coin.bitcoinTestNet:
case Coin.litecoinTestNet:
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
@ -90,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: [
@ -112,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(
@ -130,6 +178,7 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
amount: amount,
address: address,
trade: trade,
fromDesktopStep4: widget.fromDesktopStep4,
),
);
},
@ -149,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();
@ -167,7 +218,7 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
late final Trade trade;
Future<void> _send(Manager manager, {bool? shouldSendPublicFiroFunds}) async {
final _amount = Format.decimalAmountToSatoshis(amount);
final _amount = Format.decimalAmountToSatoshis(amount, manager.coin);
try {
bool wasCancelled = false;
@ -178,12 +229,23 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
return BuildingTransactionDialog(
onCancel: () {
wasCancelled = true;
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();
},
Navigator.of(context).pop();
},
),
);
},
),
@ -229,7 +291,10 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
// pop building dialog
if (mounted) {
Navigator.of(context).pop();
Navigator.of(
context,
rootNavigator: Util.isDesktop,
).pop();
}
txData["note"] =
@ -243,9 +308,12 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
builder: (_) => ConfirmChangeNowSendView(
transactionInfo: txData,
walletId: walletId,
routeOnSuccessName: HomeView.routeName,
routeOnSuccessName: Util.isDesktop
? DesktopExchangeView.routeName
: HomeView.routeName,
trade: trade,
shouldSendPublicFiroFunds: shouldSendPublicFiroFunds,
fromDesktopStep4: widget.fromDesktopStep4,
),
settings: const RouteSettings(
name: ConfirmChangeNowSendView.routeName,
@ -339,10 +407,16 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
Constants.size.circularBorderRadius,
),
),
onPressed: () => _send(
manager,
shouldSendPublicFiroFunds: false,
),
onPressed: () async {
if (mounted) {
unawaited(
_send(
manager,
shouldSendPublicFiroFunds: false,
),
);
}
},
child: Container(
color: Colors.transparent,
child: Padding(
@ -375,7 +449,8 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces: Constants.decimalPlaces,
decimalPlaces:
Constants.decimalPlacesForCoin(coin),
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
@ -418,10 +493,16 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
Constants.size.circularBorderRadius,
),
),
onPressed: () => _send(
manager,
shouldSendPublicFiroFunds: true,
),
onPressed: () async {
if (mounted) {
unawaited(
_send(
manager,
shouldSendPublicFiroFunds: true,
),
);
}
},
child: Container(
color: Colors.transparent,
child: Padding(
@ -454,7 +535,8 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces: Constants.decimalPlaces,
decimalPlaces:
Constants.decimalPlacesForCoin(coin),
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
@ -504,7 +586,13 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
Constants.size.circularBorderRadius,
),
),
onPressed: () => _send(manager),
onPressed: () async {
if (mounted) {
unawaited(
_send(manager),
);
}
},
child: child,
),
child: Row(
@ -556,11 +644,8 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces: coin == Coin.monero
? Constants.decimalPlacesMonero
: coin == Coin.wownero
? Constants.decimalPlacesWownero
: Constants.decimalPlaces,
decimalPlaces:
Constants.decimalPlacesForCoin(coin),
)} ${coin.ticker}",
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';
@ -21,6 +23,7 @@ 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';
@ -71,19 +74,53 @@ 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());
}
@ -269,48 +306,51 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
return ConditionalParent(
condition: !isDesktop,
builder: (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();
}
},
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();
}
},
),
title: Text(
"Generate QR code",
style: STextStyles.navBarTitle(context),
),
),
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: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
body: LayoutBuilder(
builder: (buildContext, 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: Padding(
@ -494,7 +534,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
});
}
: onGeneratePressed,
desktopMed: true,
buttonHeight: isDesktop ? ButtonHeight.l : null,
),
if (isDesktop && didGenerate)
Row(
@ -511,6 +551,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
width: isDesktop ? 370 : null,
child: Column(
children: [
Text(
@ -542,31 +583,39 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
height: 12,
),
Row(
mainAxisAlignment: isDesktop
? MainAxisAlignment.center
: MainAxisAlignment.start,
children: [
SecondaryButton(
width: 170,
desktopMed: true,
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)
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,
),
),
const SizedBox(
width: 16,
),
PrimaryButton(
width: 170,
desktopMed: true,
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",

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,
),
),
));
},
),
],
),
),
),
),
],
),
),
),
),

View file

@ -8,6 +8,7 @@ import 'package:stackwallet/notifications/show_flush_bar.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/providers.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/route_generator.dart';
@ -21,8 +22,11 @@ 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/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -34,6 +38,7 @@ class ConfirmTransactionView extends ConsumerStatefulWidget {
required this.transactionInfo,
required this.walletId,
this.routeOnSuccessName = WalletView.routeName,
this.isTradeTransaction = false,
}) : super(key: key);
static const String routeName = "/confirmTransactionView";
@ -41,6 +46,7 @@ class ConfirmTransactionView extends ConsumerStatefulWidget {
final Map<String, dynamic> transactionInfo;
final String walletId;
final String routeOnSuccessName;
final bool isTradeTransaction;
@override
ConsumerState<ConfirmTransactionView> createState() =>
@ -54,22 +60,17 @@ class _ConfirmTransactionViewState
late final String routeOnSuccessName;
late final bool isDesktop;
int _fee = 12;
final List<int> _dropDownItems = [
12,
22,
234,
];
Future<void> _attemptSend(BuildContext context) async {
unawaited(showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
return const SendingTransactionDialog();
},
));
unawaited(
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
return const SendingTransactionDialog();
},
),
);
final note = transactionInfo["note"] as String? ?? "";
final manager =
@ -122,25 +123,66 @@ class _ConfirmTransactionViewState
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return StackDialog(
title: "Broadcast 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>()!
.accentColorDark),
if (isDesktop) {
return DesktopDialog(
maxWidth: 450,
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Broadcast transaction failed",
style: STextStyles.desktopH3(context),
),
const SizedBox(
height: 24,
),
Text(
e.toString(),
style: STextStyles.smallMed14(context),
),
const SizedBox(
height: 56,
),
Row(
children: [
const Spacer(),
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.l,
label: "Ok",
onPressed: Navigator.of(context).pop,
),
),
],
)
],
),
),
onPressed: () {
Navigator.of(context).pop();
},
),
);
);
} else {
return StackDialog(
title: "Broadcast 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>()!
.accentColorDark),
),
onPressed: () {
Navigator.of(context).pop();
},
),
);
}
},
);
}
@ -162,48 +204,51 @@ class _ConfirmTransactionViewState
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
builder: (child) => 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(Duration(milliseconds: 50));
// }
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(Duration(milliseconds: 50));
// }
Navigator.of(context).pop();
},
),
title: Text(
"Confirm transaction",
style: STextStyles.navBarTitle(context),
),
),
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,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
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(
@ -276,13 +321,12 @@ class _ConfirmTransactionViewState
style: STextStyles.smallMed12(context),
),
Text(
"${Format.satoshiAmountToPrettyString(
transactionInfo["recipientAmt"] as int,
ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
)} ${ref.watch(
"${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),
@ -303,13 +347,12 @@ class _ConfirmTransactionViewState
style: STextStyles.smallMed12(context),
),
Text(
"${Format.satoshiAmountToPrettyString(
transactionInfo["fee"] as int,
ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
)} ${ref.watch(
"${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),
@ -453,6 +496,7 @@ class _ConfirmTransactionViewState
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
coin,
)} ${coin.ticker}",
style: STextStyles
.desktopTextExtraExtraSmall(
@ -568,32 +612,97 @@ class _ConfirmTransactionViewState
),
if (isDesktop)
Padding(
padding: const EdgeInsets.only(
top: 10,
left: 32,
right: 32,
),
child: DropdownButtonFormField(
value: _fee,
items: _dropDownItems
.map(
(e) => DropdownMenuItem(
value: e,
child: Text(
e.toString(),
),
),
)
.toList(),
onChanged: (value) {
if (value is int) {
setState(() {
_fee = value;
});
}
},
),
),
padding: const EdgeInsets.only(
top: 10,
left: 32,
right: 32,
),
child: RoundedContainer(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 18,
),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
child: Builder(builder: (context) {
final coin = ref
.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId)))
.coin;
final fee = Format.satoshisToAmount(
transactionInfo["fee"] as int,
coin: coin,
);
return Text(
"${Format.localizedStringAsFixed(
value: fee,
locale: ref.watch(localeServiceChangeNotifierProvider
.select((value) => value.locale)),
decimalPlaces: Constants.decimalPlacesForCoin(coin),
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
}),
)
// DropdownButtonHideUnderline(
// child: DropdownButton2(
// offset: const Offset(0, -10),
// isExpanded: true,
//
// dropdownElevation: 0,
// value: _fee,
// items: [
// ..._dropDownItems.map(
// (e) {
// String message = _fee.toString();
//
// return DropdownMenuItem(
// value: e,
// child: Text(message),
// );
// },
// ),
// ],
// onChanged: (value) {
// if (value is int) {
// setState(() {
// _fee = value;
// });
// }
// },
// icon: SvgPicture.asset(
// Assets.svg.chevronDown,
// width: 12,
// height: 6,
// color:
// Theme.of(context).extension<StackColors>()!.textDark3,
// ),
// buttonPadding: const EdgeInsets.symmetric(
// horizontal: 16,
// vertical: 8,
// ),
// 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,
// ),
// ),
// ),
// ),
),
if (!isDesktop) const Spacer(),
SizedBox(
height: isDesktop ? 23 : 12,
@ -640,6 +749,9 @@ class _ConfirmTransactionViewState
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
ref.watch(
managerProvider.select((value) => value.coin),
),
)} ${ref.watch(
managerProvider.select((value) => value.coin),
).ticker}",
@ -672,30 +784,79 @@ class _ConfirmTransactionViewState
: const EdgeInsets.all(0),
child: PrimaryButton(
label: "Send",
desktopMed: true,
buttonHeight: isDesktop ? ButtonHeight.l : null,
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"),
),
);
final dynamic unlocked;
if (unlocked is bool && unlocked && mounted) {
unawaited(_attemptSend(context));
final coin = ref
.read(walletsChangeNotifierProvider)
.getManager(walletId)
.coin;
if (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 (mounted) {
if (unlocked == true) {
unawaited(_attemptSend(context));
} else {
unawaited(
showFloatingFlushBar(
type: FlushBarType.warning,
message: Util.isDesktop
? "Invalid passphrase"
: "Invalid PIN",
context: context),
);
}
}
},
),

File diff suppressed because it is too large Load diff

View file

@ -77,7 +77,7 @@ class _RestoringDialogState extends State<BuildingTransactionDialog>
height: 40,
),
SecondaryButton(
desktopMed: true,
buttonHeight: ButtonHeight.l,
label: "Cancel",
onPressed: () {
onCancel.call();

View file

@ -161,7 +161,7 @@ class _FiroBalanceSelectionSheetState
ConnectionState.done &&
snapshot.hasData) {
return Text(
"${snapshot.data!} ${manager.coin.ticker}",
"${snapshot.data!.toStringAsFixed(8)} ${manager.coin.ticker}",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.left,
);
@ -251,7 +251,7 @@ class _FiroBalanceSelectionSheetState
ConnectionState.done &&
snapshot.hasData) {
return Text(
"${snapshot.data!} ${manager.coin.ticker}",
"${snapshot.data!.toStringAsFixed(8)} ${manager.coin.ticker}",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.left,
);

View file

@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.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/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class SendingTransactionDialog extends StatefulWidget {
@ -43,24 +46,56 @@ class _RestoringDialogState extends State<SendingTransactionDialog>
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: StackDialog(
title: "Sending transaction",
// // TODO get message from design team
// message: "<PLACEHOLDER>",
icon: RotationTransition(
turns: _spinAnimation,
child: SvgPicture.asset(
Assets.svg.arrowRotate,
color: Theme.of(context).extension<StackColors>()!.accentColorDark,
width: 24,
height: 24,
if (Util.isDesktop) {
return DesktopDialog(
child: Padding(
padding: const EdgeInsets.all(40),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Sending transaction",
style: STextStyles.desktopH3(context),
),
const SizedBox(
height: 40,
),
RotationTransition(
turns: _spinAnimation,
child: SvgPicture.asset(
Assets.svg.arrowRotate,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 24,
height: 24,
),
),
],
),
),
),
);
);
} else {
return WillPopScope(
onWillPop: () async {
return false;
},
child: StackDialog(
title: "Sending transaction",
// // TODO get message from design team
// message: "<PLACEHOLDER>",
icon: RotationTransition(
turns: _spinAnimation,
child: SvgPicture.asset(
Assets.svg.arrowRotate,
color:
Theme.of(context).extension<StackColors>()!.accentColorDark,
width: 24,
height: 24,
),
),
),
);
}
}
}

View file

@ -1,3 +1,4 @@
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -70,16 +71,27 @@ class _TransactionFeeSelectionSheetState
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.fast.raw!);
ref.read(feeSheetSessionCacheProvider).fast[amount] =
Format.satoshisToAmount(
fee,
coin: coin,
);
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate));
Format.satoshisToAmount(
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate),
coin: coin);
} else {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
Format.satoshisToAmount(
await manager.estimateFeeFor(amount, feeRate));
await manager.estimateFeeFor(amount, feeRate),
coin: coin);
}
}
return ref.read(feeSheetSessionCacheProvider).fast[amount]!;
@ -88,17 +100,27 @@ class _TransactionFeeSelectionSheetState
if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.regular.raw!);
ref.read(feeSheetSessionCacheProvider).average[amount] =
Format.satoshisToAmount(
fee,
coin: coin,
);
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).average[amount] =
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate));
Format.satoshisToAmount(
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate),
coin: coin);
} else {
ref.read(feeSheetSessionCacheProvider).average[amount] =
Format.satoshisToAmount(
await manager.estimateFeeFor(amount, feeRate));
await manager.estimateFeeFor(amount, feeRate),
coin: coin);
}
}
return ref.read(feeSheetSessionCacheProvider).average[amount]!;
@ -107,17 +129,27 @@ class _TransactionFeeSelectionSheetState
if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.slow.raw!);
ref.read(feeSheetSessionCacheProvider).slow[amount] =
Format.satoshisToAmount(
fee,
coin: coin,
);
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate));
Format.satoshisToAmount(
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate),
coin: coin);
} else {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
Format.satoshisToAmount(
await manager.estimateFeeFor(amount, feeRate));
await manager.estimateFeeFor(amount, feeRate),
coin: coin);
}
}
return ref.read(feeSheetSessionCacheProvider).slow[amount]!;
@ -225,7 +257,7 @@ class _TransactionFeeSelectionSheetState
ref.read(feeRateTypeStateProvider.state).state =
FeeRateType.fast;
}
String? fee = getAmount(FeeRateType.fast);
String? fee = getAmount(FeeRateType.fast, manager.coin);
if (fee != null) {
widget.updateChosen(fee);
}
@ -293,7 +325,7 @@ class _TransactionFeeSelectionSheetState
feeRate: feeObject!.fast,
amount: Format
.decimalAmountToSatoshis(
amount)),
amount, manager.coin)),
// future: manager.estimateFeeFor(
// Format.decimalAmountToSatoshis(
// amount),
@ -358,7 +390,8 @@ class _TransactionFeeSelectionSheetState
ref.read(feeRateTypeStateProvider.state).state =
FeeRateType.average;
}
String? fee = getAmount(FeeRateType.average);
String? fee =
getAmount(FeeRateType.average, manager.coin);
if (fee != null) {
widget.updateChosen(fee);
}
@ -424,7 +457,7 @@ class _TransactionFeeSelectionSheetState
feeRate: feeObject!.medium,
amount: Format
.decimalAmountToSatoshis(
amount)),
amount, manager.coin)),
// future: manager.estimateFeeFor(
// Format.decimalAmountToSatoshis(
// amount),
@ -489,7 +522,7 @@ class _TransactionFeeSelectionSheetState
ref.read(feeRateTypeStateProvider.state).state =
FeeRateType.slow;
}
String? fee = getAmount(FeeRateType.slow);
String? fee = getAmount(FeeRateType.slow, manager.coin);
print("fee $fee");
if (fee != null) {
widget.updateChosen(fee);
@ -557,7 +590,7 @@ class _TransactionFeeSelectionSheetState
feeRate: feeObject!.slow,
amount: Format
.decimalAmountToSatoshis(
amount)),
amount, manager.coin)),
// future: manager.estimateFeeFor(
// Format.decimalAmountToSatoshis(
// amount),
@ -624,10 +657,10 @@ class _TransactionFeeSelectionSheetState
);
}
String? getAmount(FeeRateType feeRateType) {
String? getAmount(FeeRateType feeRateType, Coin coin) {
try {
print(feeRateType);
var amount = Format.decimalAmountToSatoshis(this.amount);
var amount = Format.decimalAmountToSatoshis(this.amount, coin);
print(amount);
print(ref.read(feeSheetSessionCacheProvider).fast);
print(ref.read(feeSheetSessionCacheProvider).average);

View file

@ -11,6 +11,7 @@ import 'package:package_info_plus/package_info_plus.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/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -117,408 +118,431 @@ class AboutView extends ConsumerWidget {
];
Future commitMoneroFuture = Future.wait(futureMoneroList);
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
"About",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"About",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FutureBuilder(
future: PackageInfo.fromPlatform(),
builder:
(context, AsyncSnapshot<PackageInfo> snapshot) {
String version = "";
String signature = "";
String appName = "";
String build = "";
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FutureBuilder(
future: PackageInfo.fromPlatform(),
builder:
(context, AsyncSnapshot<PackageInfo> snapshot) {
String version = "";
String signature = "";
String appName = "";
String build = "";
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
version = snapshot.data!.version;
build = snapshot.data!.buildNumber;
signature = snapshot.data!.buildSignature;
appName = snapshot.data!.appName;
}
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
version = snapshot.data!.version;
build = snapshot.data!.buildNumber;
signature = snapshot.data!.buildSignature;
appName = snapshot.data!.appName;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
appName,
style: STextStyles.pageTitleH2(context),
),
),
const SizedBox(
height: 24,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Version",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
version,
style:
STextStyles.itemSubtitle(context),
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Build number",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
build,
style:
STextStyles.itemSubtitle(context),
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Build signature",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
signature,
style:
STextStyles.itemSubtitle(context),
),
],
),
),
],
);
},
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: commitFiroFuture,
builder:
(context, AsyncSnapshot<dynamic> snapshot) {
bool commitExists = false;
bool isHead = false;
CommitStatus stateOfCommit =
CommitStatus.notLoaded;
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
commitExists = snapshot.data![0] as bool;
isHead = snapshot.data![1] as bool;
if (commitExists && isHead) {
stateOfCommit = CommitStatus.isHead;
} else if (commitExists) {
stateOfCommit = CommitStatus.isOldCommit;
} else {
stateOfCommit = CommitStatus.notACommit;
}
}
TextStyle indicationStyle =
STextStyles.itemSubtitle(context);
switch (stateOfCommit) {
case CommitStatus.isHead:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen);
break;
case CommitStatus.isOldCommit:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorYellow);
break;
case CommitStatus.notACommit:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorRed);
break;
default:
break;
}
return RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Firo Build Commit",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
firoCommit,
style: indicationStyle,
),
],
),
);
}),
const SizedBox(
height: 12,
),
FutureBuilder(
future: commitEpicFuture,
builder:
(context, AsyncSnapshot<dynamic> snapshot) {
bool commitExists = false;
bool isHead = false;
CommitStatus stateOfCommit =
CommitStatus.notLoaded;
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
commitExists = snapshot.data![0] as bool;
isHead = snapshot.data![1] as bool;
if (commitExists && isHead) {
stateOfCommit = CommitStatus.isHead;
} else if (commitExists) {
stateOfCommit = CommitStatus.isOldCommit;
} else {
stateOfCommit = CommitStatus.notACommit;
}
}
TextStyle indicationStyle =
STextStyles.itemSubtitle(context);
switch (stateOfCommit) {
case CommitStatus.isHead:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen);
break;
case CommitStatus.isOldCommit:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorYellow);
break;
case CommitStatus.notACommit:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorRed);
break;
default:
break;
}
return RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Epic Cash Build Commit",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
epicCashCommit,
style: indicationStyle,
),
],
),
);
}),
const SizedBox(
height: 12,
),
FutureBuilder(
future: commitMoneroFuture,
builder:
(context, AsyncSnapshot<dynamic> snapshot) {
bool commitExists = false;
bool isHead = false;
CommitStatus stateOfCommit =
CommitStatus.notLoaded;
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
commitExists = snapshot.data![0] as bool;
isHead = snapshot.data![1] as bool;
if (commitExists && isHead) {
stateOfCommit = CommitStatus.isHead;
} else if (commitExists) {
stateOfCommit = CommitStatus.isOldCommit;
} else {
stateOfCommit = CommitStatus.notACommit;
}
}
TextStyle indicationStyle =
STextStyles.itemSubtitle(context);
switch (stateOfCommit) {
case CommitStatus.isHead:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen);
break;
case CommitStatus.isOldCommit:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorYellow);
break;
case CommitStatus.notACommit:
indicationStyle =
STextStyles.itemSubtitle(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorRed);
break;
default:
break;
}
return RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Monero Build Commit",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
moneroCommit,
style: indicationStyle,
),
],
),
);
}),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Text(
appName,
style: STextStyles.pageTitleH2(context),
),
Text(
"Website",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 24,
height: 4,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Version",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
version,
style: STextStyles.itemSubtitle(context),
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Build number",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
build,
style: STextStyles.itemSubtitle(context),
),
],
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
"Build signature",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
signature,
style: STextStyles.itemSubtitle(context),
),
],
),
BlueTextButton(
text: "https://stackwallet.com",
onTap: () {
launchUrl(
Uri.parse("https://stackwallet.com"),
mode: LaunchMode.externalApplication,
);
},
),
],
);
},
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: commitFiroFuture,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
bool commitExists = false;
bool isHead = false;
CommitStatus stateOfCommit = CommitStatus.notLoaded;
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
commitExists = snapshot.data![0] as bool;
isHead = snapshot.data![1] as bool;
if (commitExists && isHead) {
stateOfCommit = CommitStatus.isHead;
} else if (commitExists) {
stateOfCommit = CommitStatus.isOldCommit;
} else {
stateOfCommit = CommitStatus.notACommit;
}
}
TextStyle indicationStyle =
STextStyles.itemSubtitle(context);
switch (stateOfCommit) {
case CommitStatus.isHead:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen);
break;
case CommitStatus.isOldCommit:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorYellow);
break;
case CommitStatus.notACommit:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorRed);
break;
default:
break;
}
return RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Firo Build Commit",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
firoCommit,
style: indicationStyle,
),
],
),
);
}),
const SizedBox(
height: 12,
),
FutureBuilder(
future: commitEpicFuture,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
bool commitExists = false;
bool isHead = false;
CommitStatus stateOfCommit = CommitStatus.notLoaded;
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
commitExists = snapshot.data![0] as bool;
isHead = snapshot.data![1] as bool;
if (commitExists && isHead) {
stateOfCommit = CommitStatus.isHead;
} else if (commitExists) {
stateOfCommit = CommitStatus.isOldCommit;
} else {
stateOfCommit = CommitStatus.notACommit;
}
}
TextStyle indicationStyle =
STextStyles.itemSubtitle(context);
switch (stateOfCommit) {
case CommitStatus.isHead:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen);
break;
case CommitStatus.isOldCommit:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorYellow);
break;
case CommitStatus.notACommit:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorRed);
break;
default:
break;
}
return RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Epic Cash Build Commit",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
epicCashCommit,
style: indicationStyle,
),
],
),
);
}),
const SizedBox(
height: 12,
),
FutureBuilder(
future: commitMoneroFuture,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
bool commitExists = false;
bool isHead = false;
CommitStatus stateOfCommit = CommitStatus.notLoaded;
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
commitExists = snapshot.data![0] as bool;
isHead = snapshot.data![1] as bool;
if (commitExists && isHead) {
stateOfCommit = CommitStatus.isHead;
} else if (commitExists) {
stateOfCommit = CommitStatus.isOldCommit;
} else {
stateOfCommit = CommitStatus.notACommit;
}
}
TextStyle indicationStyle =
STextStyles.itemSubtitle(context);
switch (stateOfCommit) {
case CommitStatus.isHead:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen);
break;
case CommitStatus.isOldCommit:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorYellow);
break;
case CommitStatus.notACommit:
indicationStyle =
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorRed);
break;
default:
break;
}
return RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Monero Build Commit",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
SelectableText(
moneroCommit,
style: indicationStyle,
),
],
),
);
}),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Website",
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 4,
),
BlueTextButton(
text: "https://stackwallet.com",
onTap: () {
launchUrl(
Uri.parse("https://stackwallet.com"),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
const SizedBox(
height: 12,
),
const Spacer(),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: STextStyles.label(context),
children: [
const TextSpan(
text:
"By using Stack Wallet, you agree to the "),
TextSpan(
text: "Terms of service",
style: STextStyles.richLink(context),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(
Uri.parse(
"https://stackwallet.com/terms-of-service.html"),
mode: LaunchMode.externalApplication,
);
},
),
const TextSpan(text: " and "),
TextSpan(
text: "Privacy policy",
style: STextStyles.richLink(context),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(
Uri.parse(
"https://stackwallet.com/privacy-policy.html"),
mode: LaunchMode.externalApplication,
);
},
),
],
const SizedBox(
height: 12,
),
),
],
const Spacer(),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: STextStyles.label(context),
children: [
const TextSpan(
text:
"By using Stack Wallet, you agree to the "),
TextSpan(
text: "Terms of service",
style: STextStyles.richLink(context),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(
Uri.parse(
"https://stackwallet.com/terms-of-service.html"),
mode: LaunchMode.externalApplication,
);
},
),
const TextSpan(text: " and "),
TextSpan(
text: "Privacy policy",
style: STextStyles.richLink(context),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(
Uri.parse(
"https://stackwallet.com/privacy-policy.html"),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
],
),
),
),
),
);
},
);
},
),
),
),
);

View file

@ -1,16 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
import 'package:stackwallet/providers/global/prefs_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/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
class AdvancedSettingsView extends StatelessWidget {
const AdvancedSettingsView({
@ -23,158 +22,160 @@ class AdvancedSettingsView extends StatelessWidget {
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
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(
"Advanced",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Advanced",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
onPressed: () {
Navigator.of(context).pushNamed(DebugView.routeName);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
children: [
Text(
"Debug info",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
onPressed: () {
Navigator.of(context).pushNamed(DebugView.routeName);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
children: [
Text(
"Debug info",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
),
),
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
onPressed: null,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Toggle testnet coins",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider
.select((value) => value.showTestNetCoins),
),
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.showTestNetCoins = newValue;
},
onPressed: null,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Toggle testnet coins",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
),
],
),
),
);
},
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Consumer(
builder: (_, ref, __) {
final externalCalls = ref.watch(
prefsChangeNotifierProvider
.select((value) => value.externalCalls),
);
return RawMaterialButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
Navigator.of(context).pushNamed(
StackPrivacyCalls.routeName,
arguments: true,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
children: [
RichText(
textAlign: TextAlign.left,
text: TextSpan(
children: [
TextSpan(
text: "Stack Experience",
style: STextStyles.titleBold12(context),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider.select(
(value) => value.showTestNetCoins),
),
TextSpan(
text: externalCalls
? "\nEasy crypto"
: "\nIncognito",
style: STextStyles.label(context)
.copyWith(fontSize: 15.0),
)
],
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.showTestNetCoins = newValue;
},
),
),
),
],
],
),
),
),
);
},
);
},
),
),
),
],
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Consumer(
builder: (_, ref, __) {
final externalCalls = ref.watch(
prefsChangeNotifierProvider
.select((value) => value.externalCalls),
);
return RawMaterialButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
Navigator.of(context).pushNamed(
StackPrivacyCalls.routeName,
arguments: true,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
children: [
RichText(
textAlign: TextAlign.left,
text: TextSpan(
children: [
TextSpan(
text: "Stack Experience",
style: STextStyles.titleBold12(context),
),
TextSpan(
text: externalCalls
? "\nEasy crypto"
: "\nIncognito",
style: STextStyles.label(context)
.copyWith(fontSize: 15.0),
)
],
),
),
],
),
),
);
},
),
),
],
),
),
),
);

View file

@ -7,19 +7,26 @@ import 'package:event_bus/event_bus.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS;
import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:stackwallet/models/isar/models/log.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart';
import 'package:stackwallet/providers/global/debug_service_provider.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/logger.dart';
import 'package:stackwallet/utilities/stack_file_system.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/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
@ -28,15 +35,6 @@ import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS;
import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS;
import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS;
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/util.dart';
class DebugView extends ConsumerStatefulWidget {
const DebugView({Key? key}) : super(key: key);
@ -102,474 +100,484 @@ class _DebugViewState extends ConsumerState<DebugView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
"Debug",
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("deleteLogsAppBarButtonKey"),
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: () async {
await showDialog<void>(
context: context,
builder: (_) => StackDialog(
title: "Delete logs?",
message:
"You are about to delete all logs permanently. Are you sure?",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.itemSubtitle12(context),
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
"Debug",
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("deleteLogsAppBarButtonKey"),
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: () async {
await showDialog<void>(
context: context,
builder: (_) => StackDialog(
title: "Delete logs?",
message:
"You are about to delete all logs permanently. Are you sure?",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Cancel",
style: STextStyles.itemSubtitle12(context),
),
onPressed: () {
Navigator.of(context).pop();
},
),
onPressed: () {
Navigator.of(context).pop();
},
),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Delete logs",
style: STextStyles.button(context),
),
onPressed: () async {
Navigator.of(context).pop();
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Delete logs",
style: STextStyles.button(context),
),
onPressed: () async {
Navigator.of(context).pop();
bool shouldPop = false;
unawaited(showDialog<dynamic>(
barrierDismissible: false,
context: context,
builder: (_) => WillPopScope(
onWillPop: () async {
return shouldPop;
},
child: const CustomLoadingOverlay(
message: "Deleting logs...",
eventBus: null,
bool shouldPop = false;
unawaited(showDialog<dynamic>(
barrierDismissible: false,
context: context,
builder: (_) => WillPopScope(
onWillPop: () async {
return shouldPop;
},
child: const CustomLoadingOverlay(
message: "Deleting logs...",
eventBus: null,
),
),
),
));
));
await ref
.read(debugServiceProvider)
.deleteAllMessages();
await ref
.read(debugServiceProvider)
.updateRecentLogs();
await ref
.read(debugServiceProvider)
.deleteAllMessages();
await ref
.read(debugServiceProvider)
.updateRecentLogs();
shouldPop = true;
shouldPop = true;
if (mounted) {
Navigator.pop(context);
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
context: context,
message: 'Logs cleared!'));
}
},
if (mounted) {
Navigator.pop(context);
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
context: context,
message: 'Logs cleared!'));
}
},
),
),
),
);
},
);
},
),
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 16,
right: 16,
],
),
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (newString) {
setState(() => _searchTerm = newString);
},
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 = "";
});
},
),
],
),
),
)
: null,
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 16,
right: 16,
),
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
const SizedBox(
height: 12,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BlueTextButton(
text: "Save Debug Info to clipboard",
onTap: () async {
try {
final packageInfo =
await PackageInfo.fromPlatform();
final version = packageInfo.version;
final build = packageInfo.buildNumber;
final signature = packageInfo.buildSignature;
final appName = packageInfo.appName;
String firoCommit =
FIRO_VERSIONS.getPluginVersion();
String epicCashCommit =
EPIC_VERSIONS.getPluginVersion();
String moneroCommit =
MONERO_VERSIONS.getPluginVersion();
DeviceInfoPlugin deviceInfoPlugin =
DeviceInfoPlugin();
final deviceInfo =
await deviceInfoPlugin.deviceInfo;
var deviceInfoMap = deviceInfo.toMap();
deviceInfoMap.remove("systemFeatures");
final logs = filtered(
ref.watch(debugServiceProvider.select(
(value) => value.recentLogs)),
_searchTerm)
.reversed
.toList(growable: false);
List errorLogs = [];
for (var log in logs) {
if (log.logLevel == LogLevel.Error ||
log.logLevel == LogLevel.Fatal) {
errorLogs.add(
"${log.logLevel}: ${log.message}");
}
}
final finalDebugMap = {
"version": version,
"build": build,
"signature": signature,
"appName": appName,
"firoCommit": firoCommit,
"epicCashCommit": epicCashCommit,
"moneroCommit": moneroCommit,
"deviceInfoMap": deviceInfoMap,
"errorLogs": errorLogs,
};
Logging.instance.log(
json.encode(finalDebugMap),
level: LogLevel.Info,
printFullLength: true);
const ClipboardInterface clipboard =
ClipboardWrapper();
await clipboard.setData(
ClipboardData(
text: json.encode(finalDebugMap)),
);
} catch (e, s) {
Logging.instance
.log("$e $s", level: LogLevel.Error);
}
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (newString) {
setState(() => _searchTerm = newString);
},
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 = "";
});
},
),
],
),
),
)
: null,
),
),
const Spacer(),
BlueTextButton(
text: "Save logs to file",
onTap: () async {
final systemfile = StackFileSystem();
await systemfile.prepareStorage();
Directory rootPath =
(await getApplicationDocumentsDirectory());
if (Platform.isAndroid) {
rootPath = Directory("/storage/emulated/0/");
}
Directory dir =
Directory('${rootPath.path}/Documents');
if (Platform.isIOS) {
dir = Directory(rootPath.path);
}
try {
if (!dir.existsSync()) {
dir.createSync();
}
} catch (e, s) {
Logging.instance
.log("$e\n$s", level: LogLevel.Error);
}
String? path;
if (Platform.isAndroid) {
path = dir.path;
} else {
path = await FilePicker.platform
.getDirectoryPath(
dialogTitle: "Choose Log Save Location",
initialDirectory:
systemfile.startPath!.path,
lockParentWindow: true,
);
}
if (path != null) {
final eventBus = EventBus();
bool shouldPop = false;
unawaited(showDialog<dynamic>(
barrierDismissible: false,
context: context,
builder: (_) => WillPopScope(
onWillPop: () async {
return shouldPop;
},
child: CustomLoadingOverlay(
message: "Generating Stack logs file",
eventBus: eventBus,
),
),
));
bool logssaved = true;
var filename;
),
const SizedBox(
height: 12,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BlueTextButton(
text: "Save Debug Info to clipboard",
onTap: () async {
try {
filename = await ref
.read(debugServiceProvider)
.exportToFile(path, eventBus);
final packageInfo =
await PackageInfo.fromPlatform();
final version = packageInfo.version;
final build = packageInfo.buildNumber;
final signature =
packageInfo.buildSignature;
final appName = packageInfo.appName;
String firoCommit =
FIRO_VERSIONS.getPluginVersion();
String epicCashCommit =
EPIC_VERSIONS.getPluginVersion();
String moneroCommit =
MONERO_VERSIONS.getPluginVersion();
DeviceInfoPlugin deviceInfoPlugin =
DeviceInfoPlugin();
final deviceInfo =
await deviceInfoPlugin.deviceInfo;
var deviceInfoMap = deviceInfo.toMap();
deviceInfoMap.remove("systemFeatures");
final logs = filtered(
ref.watch(debugServiceProvider
.select((value) =>
value.recentLogs)),
_searchTerm)
.reversed
.toList(growable: false);
List errorLogs = [];
for (var log in logs) {
if (log.logLevel == LogLevel.Error ||
log.logLevel == LogLevel.Fatal) {
errorLogs.add(
"${log.logLevel}: ${log.message}");
}
}
final finalDebugMap = {
"version": version,
"build": build,
"signature": signature,
"appName": appName,
"firoCommit": firoCommit,
"epicCashCommit": epicCashCommit,
"moneroCommit": moneroCommit,
"deviceInfoMap": deviceInfoMap,
"errorLogs": errorLogs,
};
Logging.instance.log(
json.encode(finalDebugMap),
level: LogLevel.Info,
printFullLength: true);
const ClipboardInterface clipboard =
ClipboardWrapper();
await clipboard.setData(
ClipboardData(
text: json.encode(finalDebugMap)),
);
} catch (e, s) {
logssaved = false;
Logging.instance
.log("$e $s", level: LogLevel.Error);
}
},
),
const Spacer(),
BlueTextButton(
text: "Save logs to file",
onTap: () async {
final systemfile = SWBFileSystem();
await systemfile.prepareStorage();
Directory rootPath = await StackFileSystem
.applicationRootDirectory();
shouldPop = true;
if (Platform.isAndroid) {
rootPath =
Directory("/storage/emulated/0/");
}
if (mounted) {
Navigator.pop(context);
Directory dir =
Directory('${rootPath.path}/Documents');
if (Platform.isIOS) {
dir = Directory(rootPath.path);
}
try {
if (!dir.existsSync()) {
dir.createSync();
}
} catch (e, s) {
Logging.instance
.log("$e\n$s", level: LogLevel.Error);
}
String? path;
if (Platform.isAndroid) {
path = dir.path;
} else {
path = await FilePicker.platform
.getDirectoryPath(
dialogTitle: "Choose Log Save Location",
initialDirectory:
systemfile.startPath!.path,
lockParentWindow: true,
);
}
if (Platform.isAndroid) {
unawaited(
showDialog(
context: context,
builder: (context) => StackOkDialog(
title: logssaved
? "Logs saved to"
: "Error Saving Logs",
message: "${path!}/$filename",
if (path != null) {
final eventBus = EventBus();
bool shouldPop = false;
unawaited(showDialog<dynamic>(
barrierDismissible: false,
context: context,
builder: (_) => WillPopScope(
onWillPop: () async {
return shouldPop;
},
child: CustomLoadingOverlay(
message: "Generating Stack logs file",
eventBus: eventBus,
),
),
));
bool logssaved = true;
var filename;
try {
filename = await ref
.read(debugServiceProvider)
.exportToFile(path, eventBus);
} catch (e, s) {
logssaved = false;
Logging.instance
.log("$e $s", level: LogLevel.Error);
}
shouldPop = true;
if (mounted) {
Navigator.pop(context);
if (Platform.isAndroid) {
unawaited(
showDialog(
context: context,
builder: (context) => StackOkDialog(
title: logssaved
? "Logs saved to"
: "Error Saving Logs",
message: "${path!}/$filename",
),
),
),
);
} else {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
context: context,
message: logssaved
? 'Logs file saved'
: "Error Saving Logs",
),
);
);
} else {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
context: context,
message: logssaved
? 'Logs file saved'
: "Error Saving Logs",
),
);
}
}
}
}
},
),
],
)
],
},
),
],
)
],
),
),
),
),
),
];
},
body: Builder(
builder: (context) {
final logs = filtered(
ref.watch(debugServiceProvider
.select((value) => value.recentLogs)),
_searchTerm)
.reversed
.toList(growable: false);
];
},
body: Builder(
builder: (context) {
final logs = filtered(
ref.watch(debugServiceProvider
.select((value) => value.recentLogs)),
_searchTerm)
.reversed
.toList(growable: false);
return CustomScrollView(
reverse: true,
// shrinkWrap: true,
controller: scrollController,
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context,
return CustomScrollView(
reverse: true,
// shrinkWrap: true,
controller: scrollController,
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final log = logs[index];
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final log = logs[index];
return Container(
key: Key("log_${log.id}_${log.timestampInMillisUTC}"),
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.popupBG,
borderRadius: _borderRadius(index, logs.length),
),
child: Padding(
padding: const EdgeInsets.all(4),
child: RoundedContainer(
padding: const EdgeInsets.all(0),
return Container(
key: Key(
"log_${log.id}_${log.timestampInMillisUTC}"),
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.popupBG,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
" [${log.logLevel.name}]",
style: STextStyles.baseXS(context)
.copyWith(
fontSize: 8,
color: (log.logLevel == LogLevel.Info
? Theme.of(context)
.extension<StackColors>()!
.topNavIconGreen
: (log.logLevel ==
LogLevel.Warning
? Theme.of(context)
.extension<StackColors>()!
.topNavIconYellow
: (log.logLevel ==
LogLevel.Error
? Colors.orange
: Theme.of(context)
.extension<
StackColors>()!
.topNavIconRed))),
borderRadius: _borderRadius(index, logs.length),
),
child: Padding(
padding: const EdgeInsets.all(4),
child: RoundedContainer(
padding: const EdgeInsets.all(0),
color: Theme.of(context)
.extension<StackColors>()!
.popupBG,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
" [${log.logLevel.name}]",
style: STextStyles.baseXS(context)
.copyWith(
fontSize: 8,
color: (log.logLevel ==
LogLevel.Info
? Theme.of(context)
.extension<StackColors>()!
.topNavIconGreen
: (log.logLevel ==
LogLevel.Warning
? Theme.of(context)
.extension<
StackColors>()!
.topNavIconYellow
: (log.logLevel ==
LogLevel.Error
? Colors.orange
: Theme.of(context)
.extension<
StackColors>()!
.topNavIconRed))),
),
),
),
Text(
"[${DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC, isUtc: true)}]: ",
style: STextStyles.baseXS(context)
.copyWith(
fontSize: 8,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
Text(
"[${DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC, isUtc: true)}]: ",
style: STextStyles.baseXS(context)
.copyWith(
fontSize: 8,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
),
),
],
),
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const SizedBox(
width: 20,
),
Flexible(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SelectableText(
log.message,
style: STextStyles.baseXS(context)
.copyWith(fontSize: 8),
),
],
],
),
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const SizedBox(
width: 20,
),
),
],
),
],
Flexible(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SelectableText(
log.message,
style:
STextStyles.baseXS(context)
.copyWith(fontSize: 8),
),
],
),
),
],
),
],
),
),
),
),
);
},
childCount: logs.length,
);
},
childCount: logs.length,
),
),
),
],
);
},
],
);
},
),
),
),
),

View file

@ -8,7 +8,9 @@ import 'package:stackwallet/utilities/text_styles.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/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -18,157 +20,417 @@ class AppearanceSettingsView extends ConsumerWidget {
static const String routeName = "/appearanceSettings";
String chooseThemeType(ThemeType type) {
switch (type) {
case ThemeType.light:
return "Light theme";
case ThemeType.oceanBreeze:
return "Ocean theme";
case ThemeType.dark:
return "Dark theme";
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
"Appearance",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Appearance",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
splashColor: Theme.of(context)
.extension<StackColors>()!
.highlight,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
splashColor: Theme.of(context)
.extension<StackColors>()!
.highlight,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
onPressed: null,
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Display favorite wallets",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider.select(
(value) =>
value.showFavoriteWallets),
onPressed: null,
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Display favorite wallets",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider.select(
(value) =>
value.showFavoriteWallets),
),
onValueChanged: (newValue) {
ref
.read(
prefsChangeNotifierProvider)
.showFavoriteWallets = newValue;
},
),
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.showFavoriteWallets = newValue;
},
),
)
],
)
],
),
),
),
);
},
);
},
),
),
),
const SizedBox(
height: 10,
),
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
splashColor: Theme.of(context)
.extension<StackColors>()!
.highlight,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: null,
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Enable dark mode",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: (DB.instance.get<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme")
as String?) ==
"dark",
onValueChanged: (newValue) {
DB.instance.put<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme",
value: (newValue
? ThemeType.dark
: ThemeType.light)
.name,
);
ref
.read(colorThemeProvider.state)
.state =
StackColors.fromStackColorTheme(
newValue
? DarkColors()
: LightColors());
},
),
)
],
),
),
);
},
const SizedBox(
height: 10,
),
),
],
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
padding: const EdgeInsets.all(0),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: null,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"Choose Theme",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
const Padding(
padding: EdgeInsets.all(10),
child: ThemeOptionsView(),
),
],
),
],
),
),
),
),
],
),
),
),
),
);
},
);
},
),
),
),
);
}
}
class ThemeOptionsView extends ConsumerStatefulWidget {
const ThemeOptionsView({
Key? key,
}) : super(key: key);
@override
ConsumerState<ThemeOptionsView> createState() => _ThemeOptionsView();
}
class _ThemeOptionsView extends ConsumerState<ThemeOptionsView> {
late String _selectedTheme;
@override
void initState() {
_selectedTheme =
DB.instance.get<dynamic>(boxName: DB.boxNameTheme, key: "colorScheme")
as String? ??
"light";
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
MaterialButton(
splashColor: Colors.transparent,
hoverColor: Colors.transparent,
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
DB.instance.put<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme",
value: ThemeType.light.name,
);
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
LightColors(),
);
setState(() {
_selectedTheme = "light";
});
},
child: SizedBox(
width: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
SizedBox(
width: 10,
height: 10,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: "light",
groupValue: _selectedTheme,
onChanged: (newValue) {
if (newValue is String && newValue == "light") {
DB.instance.put<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme",
value: ThemeType.light.name,
);
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
LightColors(),
);
setState(() {
_selectedTheme = "light";
});
}
},
),
),
const SizedBox(
width: 14,
),
Text(
"Light",
style:
STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
),
],
),
],
),
),
),
const SizedBox(
height: 10,
),
MaterialButton(
splashColor: Colors.transparent,
hoverColor: Colors.transparent,
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
DB.instance.put<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme",
value: ThemeType.oceanBreeze.name,
);
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
OceanBreezeColors(),
);
setState(() {
_selectedTheme = "oceanBreeze";
});
},
child: SizedBox(
width: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
SizedBox(
width: 10,
height: 10,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: "oceanBreeze",
groupValue: _selectedTheme,
onChanged: (newValue) {
if (newValue is String && newValue == "oceanBreeze") {
DB.instance.put<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme",
value: ThemeType.oceanBreeze.name,
);
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
OceanBreezeColors(),
);
setState(() {
_selectedTheme = "oceanBreeze";
});
}
},
),
),
const SizedBox(
width: 14,
),
Text(
"Ocean Breeze",
style:
STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
),
],
),
],
),
),
),
const SizedBox(
height: 10,
),
MaterialButton(
splashColor: Colors.transparent,
hoverColor: Colors.transparent,
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
DB.instance.put<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme",
value: ThemeType.dark.name,
);
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
DarkColors(),
);
setState(() {
_selectedTheme = "dark";
});
},
child: SizedBox(
width: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
SizedBox(
width: 10,
height: 10,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: "dark",
groupValue: _selectedTheme,
onChanged: (newValue) {
if (newValue is String && newValue == "dark") {
DB.instance.put<dynamic>(
boxName: DB.boxNameTheme,
key: "colorScheme",
value: ThemeType.dark.name,
);
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
DarkColors(),
);
setState(() {
_selectedTheme = "dark";
});
}
},
),
),
const SizedBox(
width: 14,
),
Text(
"Dark",
style:
STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
),
],
),
],
),
),
),
],
);
}
}

View file

@ -8,17 +8,17 @@ 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/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_container.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';
import '../../../widgets/rounded_white_container.dart';
class BaseCurrencySettingsView extends ConsumerStatefulWidget {
const BaseCurrencySettingsView({Key? key}) : super(key: key);
@ -37,14 +37,20 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
final _searchFocusNode = FocusNode();
void onTap(int index) {
if (currenciesWithoutSelected[index] == current || current.isEmpty) {
// ignore if already selected currency
return;
if (Util.isDesktop) {
setState(() {
current = currenciesWithoutSelected[index];
});
} else {
if (currenciesWithoutSelected[index] == current || current.isEmpty) {
// ignore if already selected currency
return;
}
current = currenciesWithoutSelected[index];
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
ref.read(prefsChangeNotifierProvider).currency = current;
}
current = currenciesWithoutSelected[index];
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
ref.read(prefsChangeNotifierProvider).currency = current;
}
BorderRadius? _borderRadius(int index) {
@ -82,6 +88,15 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
@override
void initState() {
_searchController = TextEditingController();
if (Util.isDesktop) {
currenciesWithoutSelected =
ref.read(baseCurrenciesProvider).map.keys.toList();
current = ref.read(prefsChangeNotifierProvider).currency;
if (current.isNotEmpty) {
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
}
}
super.initState();
}
@ -94,51 +109,59 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
@override
Widget build(BuildContext context) {
current = ref
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
currenciesWithoutSelected = ref
.watch(baseCurrenciesProvider.select((value) => value.map))
.keys
.toList();
if (current.isNotEmpty) {
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
}
currenciesWithoutSelected = _filtered();
final isDesktop = Util.isDesktop;
if (!isDesktop) {
current = ref
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
currenciesWithoutSelected = ref
.watch(baseCurrenciesProvider.select((value) => value.map))
.keys
.toList();
if (current.isNotEmpty) {
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
}
}
currenciesWithoutSelected = _filtered();
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
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(
"Currency",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Currency",
style: STextStyles.navBarTitle(context),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 16,
right: 16,
),
child: child,
),
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 16,
right: 16,
),
child: child,
),
);
},
child: ConditionalParent(
@ -170,7 +193,7 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
Expanded(
child: SecondaryButton(
label: "Cancel",
desktopMed: true,
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
),
),
@ -180,8 +203,21 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
Expanded(
child: PrimaryButton(
label: "Save changes",
desktopMed: true,
onPressed: Navigator.of(context).pop,
buttonHeight: ButtonHeight.l,
onPressed: () {
ref.read(prefsChangeNotifierProvider).currency =
current;
if (ref
.read(prefsChangeNotifierProvider)
.externalCalls) {
ref
.read(priceAnd24hChangeNotifierProvider)
.updatePrice();
}
Navigator.of(context).pop();
},
),
),
],

View file

@ -20,11 +20,10 @@ import 'package:stackwallet/route_generator.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/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/utilities/delete_everything.dart';
class GlobalSettingsView extends StatelessWidget {
const GlobalSettingsView({
Key? key,
@ -35,254 +34,257 @@ class GlobalSettingsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
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(
"Settings",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Settings",
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,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(4),
child: Column(
children: [
SettingsListButton(
iconAssetName: Assets.svg.addressBook,
iconSize: 16,
title: "Address book",
onPressed: () {
Navigator.of(context)
.pushNamed(AddressBookView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.downloadFolder,
iconSize: 14,
title: "Stack backup & restore",
onPressed: () {
Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator.useMaterialPageRoute,
builder: (_) => const LockscreenView(
showBackButton: true,
routeOnSuccess:
StackBackupView.routeName,
biometricsCancelButtonString: "CANCEL",
biometricsLocalizedReason:
"Authenticate to access Stack backup & restore settings",
biometricsAuthenticationTitle:
"Stack backup",
),
settings: const RouteSettings(
name: "/swblockscreen"),
),
);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.lock,
iconSize: 16,
title: "Security",
onPressed: () {
Navigator.of(context)
.pushNamed(SecurityView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.dollarSign,
iconSize: 18,
title: "Currency",
onPressed: () {
Navigator.of(context).pushNamed(
BaseCurrencySettingsView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.language,
iconSize: 18,
title: "Language",
onPressed: () {
Navigator.of(context).pushNamed(
LanguageSettingsView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.node,
iconSize: 16,
title: "Manage nodes",
onPressed: () {
Navigator.of(context)
.pushNamed(ManageNodesView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.arrowRotate3,
iconSize: 18,
title: "Syncing preferences",
onPressed: () {
Navigator.of(context).pushNamed(
SyncingPreferencesView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.arrowUpRight,
iconSize: 16,
title: "Startup",
onPressed: () {
Navigator.of(context).pushNamed(
StartupPreferencesView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.sun,
iconSize: 18,
title: "Appearance",
onPressed: () {
Navigator.of(context).pushNamed(
AppearanceSettingsView.routeName);
},
),
if (Platform.isIOS)
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: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(4),
child: Column(
children: [
SettingsListButton(
iconAssetName: Assets.svg.addressBook,
iconSize: 16,
title: "Address book",
onPressed: () {
Navigator.of(context)
.pushNamed(AddressBookView.routeName);
},
),
const SizedBox(
height: 8,
),
if (Platform.isIOS)
SettingsListButton(
iconAssetName: Assets.svg.circleAlert,
iconSize: 16,
title: "Delete account",
onPressed: () async {
await Navigator.of(context)
.pushNamed(DeleteAccountView.routeName);
iconAssetName: Assets.svg.downloadFolder,
iconSize: 14,
title: "Stack backup & restore",
onPressed: () {
Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator.useMaterialPageRoute,
builder: (_) => const LockscreenView(
showBackButton: true,
routeOnSuccess:
StackBackupView.routeName,
biometricsCancelButtonString:
"CANCEL",
biometricsLocalizedReason:
"Authenticate to access Stack backup & restore settings",
biometricsAuthenticationTitle:
"Stack backup",
),
settings: const RouteSettings(
name: "/swblockscreen"),
),
);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.ellipsis,
iconSize: 18,
title: "About",
onPressed: () {
Navigator.of(context)
.pushNamed(AboutView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.solidSliders,
iconSize: 16,
title: "Advanced",
onPressed: () {
Navigator.of(context).pushNamed(
AdvancedSettingsView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.questionMessage,
iconSize: 16,
title: "Support",
onPressed: () {
Navigator.of(context)
.pushNamed(SupportView.routeName);
},
),
// TextButton(
// style: Theme.of(context)
// .textButtonTheme
// .style
// ?.copyWith(
// backgroundColor:
// MaterialStateProperty.all<Color>(
// Theme.of(context).extension<StackColors>()!.accentColorDark
// ),
// ),
// child: Text(
// "fire test notification",
// style: STextStyles.button(context),
// ),
// onPressed: () async {
// NotificationApi.showNotification2(
// title: "Test notification",
// body: "My doggy wallet",
// walletId:
// "3c5e2d70-fcc3-11ec-86a3-31a106a81c3b",
// iconAssetName:
// Assets.svg.iconFor(coin: Coin.dogecoin),
// date: DateTime.now(),
// );
// },
// ),
],
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.lock,
iconSize: 16,
title: "Security",
onPressed: () {
Navigator.of(context)
.pushNamed(SecurityView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.dollarSign,
iconSize: 18,
title: "Currency",
onPressed: () {
Navigator.of(context).pushNamed(
BaseCurrencySettingsView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.language,
iconSize: 18,
title: "Language",
onPressed: () {
Navigator.of(context).pushNamed(
LanguageSettingsView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.node,
iconSize: 16,
title: "Manage nodes",
onPressed: () {
Navigator.of(context)
.pushNamed(ManageNodesView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.arrowRotate3,
iconSize: 18,
title: "Syncing preferences",
onPressed: () {
Navigator.of(context).pushNamed(
SyncingPreferencesView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.arrowUpRight,
iconSize: 16,
title: "Startup",
onPressed: () {
Navigator.of(context).pushNamed(
StartupPreferencesView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.sun,
iconSize: 18,
title: "Appearance",
onPressed: () {
Navigator.of(context).pushNamed(
AppearanceSettingsView.routeName);
},
),
if (Platform.isIOS)
const SizedBox(
height: 8,
),
if (Platform.isIOS)
SettingsListButton(
iconAssetName: Assets.svg.circleAlert,
iconSize: 16,
title: "Delete account",
onPressed: () async {
await Navigator.of(context).pushNamed(
DeleteAccountView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.ellipsis,
iconSize: 18,
title: "About",
onPressed: () {
Navigator.of(context)
.pushNamed(AboutView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.solidSliders,
iconSize: 16,
title: "Advanced",
onPressed: () {
Navigator.of(context).pushNamed(
AdvancedSettingsView.routeName);
},
),
const SizedBox(
height: 8,
),
SettingsListButton(
iconAssetName: Assets.svg.questionMessage,
iconSize: 16,
title: "Support",
onPressed: () {
Navigator.of(context)
.pushNamed(SupportView.routeName);
},
),
// TextButton(
// style: Theme.of(context)
// .textButtonTheme
// .style
// ?.copyWith(
// backgroundColor:
// MaterialStateProperty.all<Color>(
// Theme.of(context).extension<StackColors>()!.accentColorDark
// ),
// ),
// child: Text(
// "fire test notification",
// style: STextStyles.button(context),
// ),
// onPressed: () async {
// NotificationApi.showNotification2(
// title: "Test notification",
// body: "My doggy wallet",
// walletId:
// "3c5e2d70-fcc3-11ec-86a3-31a106a81c3b",
// iconAssetName:
// Assets.svg.iconFor(coin: Coin.dogecoin),
// date: DateTime.now(),
// );
// },
// ),
],
),
),
),
const SizedBox(
height: 12,
),
],
const SizedBox(
height: 12,
),
],
),
),
),
),
),
),
);
},
);
},
),
),
);
}

View file

@ -5,9 +5,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/debug_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.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/widgets/background.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class HiddenSettings extends StatelessWidget {
@ -17,149 +18,187 @@ class HiddenSettings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: Container(),
title: Text(
"Not so secret anymore",
style: STextStyles.navBarTitle(context),
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: Container(),
title: Text(
"Not so secret anymore",
style: STextStyles.navBarTitle(context),
),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Consumer(builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
final notifs =
ref.read(notificationsProvider).notifications;
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Consumer(builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
final notifs =
ref.read(notificationsProvider).notifications;
for (final n in notifs) {
for (final n in notifs) {
await ref
.read(notificationsProvider)
.delete(n, false);
}
await ref
.read(notificationsProvider)
.delete(n, false);
.delete(notifs[0], true);
unawaited(showFloatingFlushBar(
type: FlushBarType.success,
message: "Notification history deleted",
context: context,
));
},
child: RoundedWhiteContainer(
child: Text(
"Delete notifications",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
}),
// const SizedBox(
// height: 12,
// ),
// Consumer(builder: (_, ref, __) {
// return GestureDetector(
// onTap: () async {
// final trades =
// ref.read(tradesServiceProvider).trades;
//
// for (final trade in trades) {
// ref.read(tradesServiceProvider).delete(
// trade: trade, shouldNotifyListeners: false);
// }
// ref.read(tradesServiceProvider).delete(
// trade: trades[0], shouldNotifyListeners: true);
//
// // ref.read(notificationsProvider).DELETE_EVERYTHING();
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Delete trade history",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
// ),
// ),
// ),
// );
// }),
const SizedBox(
height: 12,
),
Consumer(builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
await ref
.read(debugServiceProvider)
.deleteAllMessages();
unawaited(showFloatingFlushBar(
type: FlushBarType.success,
message: "Debug Logs deleted",
context: context,
));
},
child: RoundedWhiteContainer(
child: Text(
"Delete Debug Logs",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
}),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
if (ref.watch(prefsChangeNotifierProvider
.select((value) => value.familiarity)) <
6) {
return GestureDetector(
onTap: () async {
final familiarity = ref
.read(prefsChangeNotifierProvider)
.familiarity;
if (familiarity < 6) {
ref
.read(prefsChangeNotifierProvider)
.familiarity = 6;
Constants.exchangeForExperiencedUsers(6);
}
},
child: RoundedWhiteContainer(
child: Text(
"Enable exchange",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
} else {
return Container();
}
await ref
.read(notificationsProvider)
.delete(notifs[0], true);
unawaited(showFloatingFlushBar(
type: FlushBarType.success,
message: "Notification history deleted",
context: context,
));
},
child: RoundedWhiteContainer(
child: Text(
"Delete notifications",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
}),
// const SizedBox(
// height: 12,
// ),
// Consumer(builder: (_, ref, __) {
// return GestureDetector(
// onTap: () async {
// final trades =
// ref.read(tradesServiceProvider).trades;
//
// for (final trade in trades) {
// ref.read(tradesServiceProvider).delete(
// trade: trade, shouldNotifyListeners: false);
// }
// ref.read(tradesServiceProvider).delete(
// trade: trades[0], shouldNotifyListeners: true);
//
// // ref.read(notificationsProvider).DELETE_EVERYTHING();
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Delete trade history",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
// ),
// ),
// ),
// );
// }),
const SizedBox(
height: 12,
),
Consumer(builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
await ref
.read(debugServiceProvider)
.deleteAllMessages();
unawaited(showFloatingFlushBar(
type: FlushBarType.success,
message: "Debug Logs deleted",
context: context,
));
},
child: RoundedWhiteContainer(
child: Text(
"Delete Debug Logs",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
}),
// const SizedBox(
// height: 12,
// ),
// GestureDetector(
// onTap: () async {
// showDialog<void>(
// context: context,
// builder: (_) {
// return StackDialogBase(
// child: SizedBox(
// width: 200,
// child: Lottie.asset(
// Assets.lottie.test2,
// ),
// ),
// );
// },
// );
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Lottie test",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
// ),
// ),
// ),
// ),
],
),
// const SizedBox(
// height: 12,
// ),
// GestureDetector(
// onTap: () async {
// showDialog<void>(
// context: context,
// builder: (_) {
// return StackDialogBase(
// child: SizedBox(
// width: 200,
// child: Lottie.asset(
// Assets.lottie.test2,
// ),
// ),
// );
// },
// );
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Lottie test",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
// ),
// ),
// ),
// ),
],
),
),
),
),
);
},
);
},
),
),
),
);

View file

@ -7,14 +7,14 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/languages_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/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class LanguageSettingsView extends ConsumerStatefulWidget {
const LanguageSettingsView({Key? key}) : super(key: key);
@ -100,203 +100,205 @@ class _LanguageViewState extends ConsumerState<LanguageSettingsView> {
listWithoutSelected.insert(0, current);
}
listWithoutSelected = _filtered();
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(
"Language",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Language",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 16,
right: 16,
),
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (newString) {
setState(() => filter = newString);
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 16,
right: 16,
),
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (newString) {
setState(() => filter = newString);
},
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: 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 = "";
});
},
),
],
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,
)
: null,
),
),
),
),
),
),
),
];
},
body: Builder(
builder: (context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context,
];
},
body: Builder(
builder: (context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.popupBG,
borderRadius: _borderRadius(index),
),
child: Padding(
padding: const EdgeInsets.all(4),
key: Key(
"languageSelect_${listWithoutSelected[index]}"),
child: RoundedContainer(
padding: const EdgeInsets.all(0),
color: index == 0
? Theme.of(context)
.extension<StackColors>()!
.currencyListItemBG
: Theme.of(context)
.extension<StackColors>()!
.popupBG,
child: RawMaterialButton(
onPressed: () async {
onTap(index);
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.popupBG,
borderRadius: _borderRadius(index),
),
child: Padding(
padding: const EdgeInsets.all(4),
key: Key(
"languageSelect_${listWithoutSelected[index]}"),
child: RoundedContainer(
padding: const EdgeInsets.all(0),
color: index == 0
? Theme.of(context)
.extension<StackColors>()!
.currencyListItemBG
: Theme.of(context)
.extension<StackColors>()!
.popupBG,
child: RawMaterialButton(
onPressed: () async {
onTap(index);
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: true,
groupValue: index == 0,
onChanged: (_) {
onTap(index);
},
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: true,
groupValue: index == 0,
onChanged: (_) {
onTap(index);
},
),
),
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
listWithoutSelected[index],
key: (index == 0)
? const Key(
"selectedLanguageSettingsLanguageText")
: null,
style: STextStyles.largeMedium14(
context),
),
const SizedBox(
height: 2,
),
Text(
listWithoutSelected[index],
key: (index == 0)
? const Key(
"selectedLanguageSettingsLanguageTextDescription")
: null,
style: STextStyles.itemSubtitle(
context),
),
],
),
],
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
listWithoutSelected[index],
key: (index == 0)
? const Key(
"selectedLanguageSettingsLanguageText")
: null,
style: STextStyles.largeMedium14(
context),
),
const SizedBox(
height: 2,
),
Text(
listWithoutSelected[index],
key: (index == 0)
? const Key(
"selectedLanguageSettingsLanguageTextDescription")
: null,
style: STextStyles.itemSubtitle(
context),
),
],
),
],
),
),
),
),
),
),
);
},
childCount: listWithoutSelected.length,
);
},
childCount: listWithoutSelected.length,
),
),
),
],
);
},
],
);
},
),
),
),
),

View file

@ -3,16 +3,15 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/test_epic_box_connection.dart';
@ -20,6 +19,7 @@ import 'package:stackwallet/utilities/test_monero_node_connection.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';
@ -40,9 +40,6 @@ class AddEditNodeView extends ConsumerStatefulWidget {
required this.coin,
required this.nodeId,
required this.routeOnSuccessOrDelete,
this.secureStore = const SecureStorageWrapper(
FlutterSecureStorage(),
),
}) : super(key: key);
static const String routeName = "/addEditNode";
@ -51,7 +48,6 @@ class AddEditNodeView extends ConsumerStatefulWidget {
final Coin coin;
final String routeOnSuccessOrDelete;
final String? nodeId;
final FlutterSecureStorageInterface secureStore;
@override
ConsumerState<AddEditNodeView> createState() => _AddEditNodeViewState();
@ -242,7 +238,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
Expanded(
child: SecondaryButton(
label: "Cancel",
desktopMed: true,
buttonHeight:
isDesktop ? ButtonHeight.l : null,
onPressed: () => Navigator.of(
context,
rootNavigator: true,
@ -255,7 +252,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
Expanded(
child: PrimaryButton(
label: "Save",
desktopMed: true,
buttonHeight:
isDesktop ? ButtonHeight.l : null,
onPressed: () => Navigator.of(
context,
rootNavigator: true,
@ -413,84 +411,95 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
return ConditionalParent(
condition: !isDesktop,
builder: (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(
viewType == AddEditNodeViewType.edit ? "Edit node" : "Add node",
style: STextStyles.navBarTitle(context),
),
actions: [
if (viewType == AddEditNodeViewType.edit)
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("deleteNodeAppBarButtonKey"),
size: 36,
shadows: const [],
color:
Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.trash,
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(
viewType == AddEditNodeViewType.edit ? "Edit node" : "Add node",
style: STextStyles.navBarTitle(context),
),
actions: [
if (viewType == AddEditNodeViewType.edit &&
ref
.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodesFor(coin)))
.length >
1)
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("deleteNodeAppBarButtonKey"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () async {
Navigator.popUntil(context,
ModalRoute.withName(widget.routeOnSuccessOrDelete));
.background,
icon: SvgPicture.asset(
Assets.svg.trash,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () async {
Navigator.popUntil(context,
ModalRoute.withName(widget.routeOnSuccessOrDelete));
await ref.read(nodeServiceChangeNotifierProvider).delete(
nodeId!,
true,
);
},
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
bottom: 12,
),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(4),
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: constraints.maxHeight - 8),
child: IntrinsicHeight(
child: child,
await ref
.read(nodeServiceChangeNotifierProvider)
.delete(
nodeId!,
true,
);
},
),
),
),
);
},
],
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
bottom: 12,
),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(4),
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: constraints.maxHeight - 8),
child: IntrinsicHeight(
child: child,
),
),
),
);
},
),
),
),
),
@ -498,6 +507,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
condition: isDesktop,
builder: (child) => DesktopDialog(
maxWidth: 580,
maxHeight: 500,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -533,7 +543,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
children: [
NodeForm(
node: node,
secureStore: widget.secureStore,
secureStore: ref.read(secureStoreProvider),
readOnly: false,
coin: widget.coin,
onChanged: (canSave, canTest) {
@ -565,7 +575,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
child: SecondaryButton(
label: "Test connection",
enabled: testConnectionEnabled,
desktopMed: true,
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: testConnectionEnabled
? () async {
await _testConnection();
@ -582,7 +592,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
child: PrimaryButton(
label: "Save",
enabled: saveEnabled,
desktopMed: true,
buttonHeight: ButtonHeight.l,
onPressed: saveEnabled ? attemptSave : null,
),
),
@ -638,7 +648,7 @@ class NodeForm extends ConsumerStatefulWidget {
}) : super(key: key);
final NodeModel? node;
final FlutterSecureStorageInterface secureStore;
final SecureStorageInterface secureStore;
final bool readOnly;
final Coin coin;
final void Function(bool canSave, bool canTestConnection)? onChanged;
@ -834,110 +844,100 @@ class _NodeFormState extends ConsumerState<NodeForm> {
const SizedBox(
height: 8,
),
Row(
children: [
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
key: const Key("addCustomNodeNodeAddressFieldKey"),
readOnly: widget.readOnly,
enabled: enableField(_hostController),
controller: _hostController,
focusNode: _hostFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
(widget.coin != Coin.monero &&
widget.coin != Coin.wownero &&
widget.coin != Coin.epicCash)
? "IP address"
: "Url",
_hostFocusNode,
context,
).copyWith(
suffixIcon:
!widget.readOnly && _hostController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
_hostController.text = "";
_updateState();
},
),
],
),
),
)
: null,
),
onChanged: (newValue) {
_updateState();
setState(() {});
},
),
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
key: const Key("addCustomNodeNodeAddressFieldKey"),
readOnly: widget.readOnly,
enabled: enableField(_hostController),
controller: _hostController,
focusNode: _hostFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
(widget.coin != Coin.monero &&
widget.coin != Coin.wownero &&
widget.coin != Coin.epicCash)
? "IP address"
: "Url",
_hostFocusNode,
context,
).copyWith(
suffixIcon: !widget.readOnly && _hostController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
_hostController.text = "";
_updateState();
},
),
],
),
),
)
: null,
),
const SizedBox(
width: 12,
onChanged: (newValue) {
_updateState();
setState(() {});
},
),
),
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
key: const Key("addCustomNodeNodePortFieldKey"),
readOnly: widget.readOnly,
enabled: enableField(_portController),
controller: _portController,
focusNode: _portFocusNode,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
keyboardType: TextInputType.number,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Port",
_portFocusNode,
context,
).copyWith(
suffixIcon: !widget.readOnly && _portController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
_portController.text = "";
_updateState();
},
),
],
),
),
)
: null,
),
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
key: const Key("addCustomNodeNodePortFieldKey"),
readOnly: widget.readOnly,
enabled: enableField(_portController),
controller: _portController,
focusNode: _portFocusNode,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
keyboardType: TextInputType.number,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Port",
_portFocusNode,
context,
).copyWith(
suffixIcon:
!widget.readOnly && _portController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
_portController.text = "";
_updateState();
},
),
],
),
),
)
: null,
),
onChanged: (newValue) {
_updateState();
setState(() {});
},
),
),
),
],
onChanged: (newValue) {
_updateState();
setState(() {});
},
),
),
const SizedBox(
height: 8,

View file

@ -8,6 +8,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/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
@ -122,66 +123,70 @@ class _CoinNodesViewState extends ConsumerState<CoinNodesView> {
),
);
} else {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"${widget.coin.prettyName} nodes",
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("manageNodesAddNewNodeButtonKey"),
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,
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"${widget.coin.prettyName} nodes",
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("manageNodesAddNewNodeButtonKey"),
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(
AddEditNodeView.routeName,
arguments: Tuple4(
AddEditNodeViewType.add,
widget.coin,
null,
CoinNodesView.routeName,
),
);
},
),
onPressed: () {
Navigator.of(context).pushNamed(
AddEditNodeView.routeName,
arguments: Tuple4(
AddEditNodeViewType.add,
widget.coin,
null,
CoinNodesView.routeName,
),
);
},
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
],
),
child: SingleChildScrollView(
child: NodesList(
coin: widget.coin,
popBackToRoute: CoinNodesView.routeName,
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
),
child: SingleChildScrollView(
child: NodesList(
coin: widget.coin,
popBackToRoute: CoinNodesView.routeName,
),
),
),
),

View file

@ -8,6 +8,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';
@ -47,88 +48,91 @@ class _ManageNodesViewState extends ConsumerState<ManageNodesView> {
? _coins
: _coins.sublist(0, _coins.length - kTestNetCoinCount);
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(
"Manage nodes",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Manage nodes",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...coins.map(
(coin) {
final count = ref
.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodesFor(coin)))
.length;
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...coins.map(
(coin) {
final count = ref
.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodesFor(coin)))
.length;
return Padding(
padding: const EdgeInsets.all(4),
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
return Padding(
padding: const EdgeInsets.all(4),
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.of(context).pushNamed(
CoinNodesView.routeName,
arguments: coin,
);
},
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
width: 24,
height: 24,
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${coin.prettyName} nodes",
style: STextStyles.titleBold12(context),
),
Text(
count > 1 ? "$count nodes" : "Default",
style: STextStyles.label(context),
),
],
)
],
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.of(context).pushNamed(
CoinNodesView.routeName,
arguments: coin,
);
},
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
width: 24,
height: 24,
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${coin.prettyName} nodes",
style: STextStyles.titleBold12(context),
),
Text(
count > 1 ? "$count nodes" : "Default",
style: STextStyles.label(context),
),
],
)
],
),
),
),
),
),
);
},
),
],
);
},
),
],
),
),
),
),

View file

@ -2,15 +2,14 @@ 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:flutter_svg/svg.dart';
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/test_epic_box_connection.dart';
@ -18,6 +17,7 @@ import 'package:stackwallet/utilities/test_monero_node_connection.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/delete_button.dart';
@ -32,14 +32,10 @@ class NodeDetailsView extends ConsumerStatefulWidget {
required this.coin,
required this.nodeId,
required this.popRouteName,
this.secureStore = const SecureStorageWrapper(
FlutterSecureStorage(),
),
}) : super(key: key);
static const String routeName = "/nodeDetails";
final FlutterSecureStorageInterface secureStore;
final Coin coin;
final String nodeId;
final String popRouteName;
@ -49,7 +45,7 @@ class NodeDetailsView extends ConsumerStatefulWidget {
}
class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
late final FlutterSecureStorageInterface secureStore;
late final SecureStorageInterface secureStore;
late final Coin coin;
late final String nodeId;
late final String popRouteName;
@ -58,7 +54,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
@override
initState() {
secureStore = widget.secureStore;
secureStore = ref.read(secureStoreProvider);
coin = widget.coin;
nodeId = widget.nodeId;
popRouteName = widget.popRouteName;
@ -181,28 +177,35 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
final node = ref.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodeById(id: nodeId)));
final nodesForCoin = ref.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodesFor(coin)));
final canDelete = nodesForCoin.length > 1;
return ConditionalParent(
condition: !isDesktop,
builder: (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(
"Node details",
style: STextStyles.navBarTitle(context),
),
actions: [
if (!nodeId.startsWith("default"))
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(
"Node details",
style: STextStyles.navBarTitle(context),
),
actions: [
// if (!nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix))
Padding(
padding: const EdgeInsets.only(
top: 10,
@ -239,29 +242,30 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
],
),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(4),
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: constraints.maxHeight - 8),
child: IntrinsicHeight(
child: child,
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 12,
right: 12,
),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(4),
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: constraints.maxHeight - 8),
child: IntrinsicHeight(
child: child,
),
),
),
),
);
},
);
},
),
),
),
),
@ -314,7 +318,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
const SizedBox(
height: 22,
),
if (isDesktop)
if (isDesktop && canDelete)
SizedBox(
height: 56,
child: _desktopReadOnly
@ -344,7 +348,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
],
),
),
if (isDesktop && !_desktopReadOnly)
if (isDesktop && !_desktopReadOnly && canDelete)
const SizedBox(
height: 45,
),
@ -353,7 +357,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
Expanded(
child: SecondaryButton(
label: "Test connection",
desktopMed: true,
buttonHeight: isDesktop ? ButtonHeight.l : null,
onPressed: () async {
await _testConnection(ref, context);
},
@ -365,22 +369,41 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
),
if (isDesktop)
Expanded(
child: !nodeId.startsWith("default")
? PrimaryButton(
label: _desktopReadOnly ? "Edit" : "Save",
desktopMed: true,
onPressed: () async {
final shouldSave = _desktopReadOnly == false;
setState(() {
_desktopReadOnly = !_desktopReadOnly;
});
child:
// !nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix)
// ?
PrimaryButton(
label: _desktopReadOnly ? "Edit" : "Save",
buttonHeight: ButtonHeight.l,
onPressed: () async {
final shouldSave = _desktopReadOnly == false;
setState(() {
_desktopReadOnly = !_desktopReadOnly;
});
if (shouldSave) {
// todo save node
}
},
)
: Container(),
if (shouldSave) {
final editedNode = node!.copyWith(
host: ref.read(nodeFormDataProvider).host,
port: ref.read(nodeFormDataProvider).port,
name: ref.read(nodeFormDataProvider).name,
useSSL: ref.read(nodeFormDataProvider).useSSL,
loginName: ref.read(nodeFormDataProvider).login,
isFailover:
ref.read(nodeFormDataProvider).isFailover,
);
await ref
.read(nodeServiceChangeNotifierProvider)
.edit(
editedNode,
ref.read(nodeFormDataProvider).password,
true,
);
}
},
)
// : Container()
,
),
],
),

View file

@ -1,33 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/security_views/security_view.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
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';
class ChangePinView extends StatefulWidget {
class ChangePinView extends ConsumerStatefulWidget {
const ChangePinView({
Key? key,
this.secureStore = const SecureStorageWrapper(
FlutterSecureStorage(),
),
}) : super(key: key);
static const String routeName = "/changePin";
final FlutterSecureStorageInterface secureStore;
@override
State<ChangePinView> createState() => _ChangePinViewState();
ConsumerState<ChangePinView> createState() => _ChangePinViewState();
}
class _ChangePinViewState extends State<ChangePinView> {
class _ChangePinViewState extends ConsumerState<ChangePinView> {
BoxDecoration get _pinPutDecoration {
return BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.textSubtitle2,
@ -49,11 +46,11 @@ class _ChangePinViewState extends State<ChangePinView> {
final TextEditingController _pinPutController2 = TextEditingController();
final FocusNode _pinPutFocusNode2 = FocusNode();
late final FlutterSecureStorageInterface _secureStore;
late final SecureStorageInterface _secureStore;
@override
void initState() {
_secureStore = widget.secureStore;
_secureStore = ref.read(secureStoreProvider);
super.initState();
}
@ -69,182 +66,186 @@ class _ChangePinViewState extends State<ChangePinView> {
@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();
}
},
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();
}
},
),
),
),
body: SafeArea(
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
// page 1
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text(
"Create new PIN",
style: STextStyles.pageTitleH1(context),
body: SafeArea(
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
// page 1
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text(
"Create new 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: _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,
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: [
Center(
child: Text(
"Confirm new PIN",
style: STextStyles.pageTitleH1(context),
),
),
const SizedBox(
height: 52,
),
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 {
if (_pinPutController1.text == _pinPutController2.text) {
// This should never fail as we are overwriting the existing pin
assert(
(await _secureStore.read(key: "stack_pin")) != null);
await _secureStore.write(key: "stack_pin", value: pin);
showFloatingFlushBar(
type: FlushBarType.success,
message: "New PIN is set up",
context: context,
iconAsset: Assets.svg.check,
);
await Future<void>.delayed(
const Duration(milliseconds: 1200));
if (mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(SecurityView.routeName),
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration,
onSubmit: (String pin) {
if (pin.length == Constants.pinLength) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
}
} else {
_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
_pinPutController1.text = '';
_pinPutController2.text = '';
}
},
),
],
),
],
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text(
"Confirm new PIN",
style: STextStyles.pageTitleH1(context),
),
),
const SizedBox(
height: 52,
),
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 {
if (_pinPutController1.text == _pinPutController2.text) {
// This should never fail as we are overwriting the existing pin
assert((await _secureStore.read(key: "stack_pin")) !=
null);
await _secureStore.write(key: "stack_pin", value: pin);
showFloatingFlushBar(
type: FlushBarType.success,
message: "New PIN is set up",
context: context,
iconAsset: Assets.svg.check,
);
await Future<void>.delayed(
const Duration(milliseconds: 1200));
if (mounted) {
Navigator.of(context).popUntil(
ModalRoute.withName(SecurityView.routeName),
);
}
} else {
_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

@ -7,6 +7,7 @@ import 'package:stackwallet/route_generator.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/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -22,128 +23,131 @@ class SecurityView extends StatelessWidget {
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
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(
"Security",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Security",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator.useMaterialPageRoute,
builder: (_) => const LockscreenView(
showBackButton: true,
routeOnSuccess: ChangePinView.routeName,
biometricsCancelButtonString: "CANCEL",
biometricsLocalizedReason: "Authenticate to change PIN",
biometricsAuthenticationTitle: "Change PIN",
),
settings:
const RouteSettings(name: "/changepinlockscreen"),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
children: [
Text(
"Change PIN",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
onPressed: () {
Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
RouteGenerator.useMaterialPageRoute,
builder: (_) => const LockscreenView(
showBackButton: true,
routeOnSuccess: ChangePinView.routeName,
biometricsCancelButtonString: "CANCEL",
biometricsLocalizedReason:
"Authenticate to change PIN",
biometricsAuthenticationTitle: "Change PIN",
),
settings:
const RouteSettings(name: "/changepinlockscreen"),
),
],
);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
children: [
Text(
"Change PIN",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
),
),
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
onPressed: null,
// () {
// final useBio =
// ref.read(prefsChangeNotifierProvider).useBiometrics;
//
// debugPrint("useBio: $useBio");
// ref.read(prefsChangeNotifierProvider).useBiometrics =
// !useBio;
//
// debugPrint(
// "useBio set to: ${ref.read(prefsChangeNotifierProvider).useBiometrics}");
// },
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Enable biometric authentication",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider
.select((value) => value.useBiometrics),
),
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.useBiometrics = newValue;
},
onPressed: null,
// () {
// final useBio =
// ref.read(prefsChangeNotifierProvider).useBiometrics;
//
// debugPrint("useBio: $useBio");
// ref.read(prefsChangeNotifierProvider).useBiometrics =
// !useBio;
//
// debugPrint(
// "useBio set to: ${ref.read(prefsChangeNotifierProvider).useBiometrics}");
// },
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Enable biometric authentication",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
),
],
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider
.select((value) => value.useBiometrics),
),
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.useBiometrics = newValue;
},
),
),
],
),
),
),
);
},
);
},
),
),
),
],
],
),
),
),
);

View file

@ -11,6 +11,8 @@ import 'package:stackwallet/utilities/enums/backup_frequency_type.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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
@ -19,8 +21,6 @@ import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:stackwallet/utilities/util.dart';
class AutoBackupView extends ConsumerStatefulWidget {
const AutoBackupView({Key? key}) : super(key: key);
@ -225,239 +225,241 @@ class _AutoBackupViewState extends ConsumerState<AutoBackupView> {
frequencyController.text = Format.prettyFrequencyType(next);
});
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(
"Auto Backup",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Auto Backup",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: null,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Auto Backup",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
key: const Key("autoBackupToggleButtonKey"),
isOn: _toggle,
controller: toggleController,
onValueChanged: (newValue) async {
_toggle = newValue;
if (_toggle) {
attemptEnable();
} else {
attemptDisable();
}
},
),
),
],
),
),
),
),
const SizedBox(
height: 8,
),
if (!isEnabledAutoBackup)
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
child: RichText(
textAlign: TextAlign.left,
text: TextSpan(
style: STextStyles.label(context),
children: [
const TextSpan(
text:
"Auto Backup is a custom Stack Wallet feature that offers a convenient backup of your data.\n\nTo ensure maximum security, we recommend using a unique password that you haven't used anywhere else on the internet before. Your password is not stored.\n\nFor more information, please see our website "),
TextSpan(
text: "stackwallet.com.",
style: STextStyles.richLink(context),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(
Uri.parse("https://stackwallet.com"),
mode: LaunchMode.externalApplication,
);
},
),
],
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
),
if (isEnabledAutoBackup)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RoundedWhiteContainer(
onPressed: null,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BlueTextButton(
text: "Back up now",
onTap: () {
ref.read(autoSWBServiceProvider).doBackup();
},
),
Text(
"Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}",
style: STextStyles.itemSubtitle(context),
)
"Auto Backup",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
key: const Key("autoBackupToggleButtonKey"),
isOn: _toggle,
controller: toggleController,
onValueChanged: (newValue) async {
_toggle = newValue;
if (_toggle) {
attemptEnable();
} else {
attemptDisable();
}
},
),
),
],
),
),
const SizedBox(
height: 32,
),
Text(
"Auto Backup file",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("backupSavedToFileLocationTextFieldKey"),
focusNode: fileLocationFocusNode,
controller: fileLocationController,
enabled: false,
style: STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark
.withOpacity(0.5),
),
readOnly: true,
enableSuggestions: false,
autocorrect: false,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: true,
),
decoration: standardInputDecoration(
"Saved to",
fileLocationFocusNode,
context,
),
),
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("backupPasswordFieldKey"),
focusNode: passwordFocusNode,
controller: passwordController,
enabled: false,
style: STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark
.withOpacity(0.5),
),
obscureText: true,
enableSuggestions: false,
autocorrect: false,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: true,
),
decoration: standardInputDecoration(
"Passphrase",
passwordFocusNode,
context,
),
),
),
const SizedBox(
height: 12,
),
Text(
"Auto Backup frequency",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 10,
),
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
key: const Key("backupFrequencyFieldKey"),
controller: frequencyController,
enabled: false,
style: STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark
.withOpacity(0.5),
),
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: true,
),
),
const SizedBox(
height: 20,
),
Center(
child: BlueTextButton(
text: "Edit Auto Backup",
onTap: () async {
Navigator.of(context)
.pushNamed(EditAutoBackupView.routeName);
},
),
)
],
),
),
],
const SizedBox(
height: 8,
),
if (!isEnabledAutoBackup)
RoundedWhiteContainer(
child: RichText(
textAlign: TextAlign.left,
text: TextSpan(
style: STextStyles.label(context),
children: [
const TextSpan(
text:
"Auto Backup is a custom Stack Wallet feature that offers a convenient backup of your data.\n\nTo ensure maximum security, we recommend using a unique password that you haven't used anywhere else on the internet before. Your password is not stored.\n\nFor more information, please see our website "),
TextSpan(
text: "stackwallet.com.",
style: STextStyles.richLink(context),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(
Uri.parse("https://stackwallet.com"),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
if (isEnabledAutoBackup)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BlueTextButton(
text: "Back up now",
onTap: () {
ref.read(autoSWBServiceProvider).doBackup();
},
),
Text(
"Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}",
style: STextStyles.itemSubtitle(context),
)
],
),
),
const SizedBox(
height: 32,
),
Text(
"Auto Backup file",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("backupSavedToFileLocationTextFieldKey"),
focusNode: fileLocationFocusNode,
controller: fileLocationController,
enabled: false,
style: STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark
.withOpacity(0.5),
),
readOnly: true,
enableSuggestions: false,
autocorrect: false,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: true,
),
decoration: standardInputDecoration(
"Saved to",
fileLocationFocusNode,
context,
),
),
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("backupPasswordFieldKey"),
focusNode: passwordFocusNode,
controller: passwordController,
enabled: false,
style: STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark
.withOpacity(0.5),
),
obscureText: true,
enableSuggestions: false,
autocorrect: false,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: true,
),
decoration: standardInputDecoration(
"Passphrase",
passwordFocusNode,
context,
),
),
),
const SizedBox(
height: 12,
),
Text(
"Auto Backup frequency",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 10,
),
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
key: const Key("backupFrequencyFieldKey"),
controller: frequencyController,
enabled: false,
style: STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark
.withOpacity(0.5),
),
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: true,
),
),
const SizedBox(
height: 20,
),
Center(
child: BlueTextButton(
text: "Edit Auto Backup",
onTap: () async {
Navigator.of(context)
.pushNamed(EditAutoBackupView.routeName);
},
),
)
],
),
],
),
),
),
);

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.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';
@ -12,75 +13,77 @@ class CreateBackupInfoView extends StatelessWidget {
@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));
}
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));
}
Navigator.of(context).pop();
},
),
title: Text(
"Create backup",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Create backup",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"Info",
style: STextStyles.pageTitleH2(context),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"Info",
style: STextStyles.pageTitleH2(context),
),
),
),
const SizedBox(
height: 16,
),
RoundedWhiteContainer(
child: Text(
// TODO: need info
"{lorem ipsum}",
style: STextStyles.baseXS(context),
const SizedBox(
height: 16,
),
),
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () {
Navigator.of(context)
.pushNamed(CreateBackupView.routeName);
},
child: Text(
"Next",
style: STextStyles.button(context),
RoundedWhiteContainer(
child: Text(
// TODO: need info
"{lorem ipsum}",
style: STextStyles.baseXS(context),
),
),
),
],
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () {
Navigator.of(context)
.pushNamed(CreateBackupView.routeName);
},
child: Text(
"Next",
style: STextStyles.button(context),
),
),
],
),
),
),
),
);
},
);
},
),
),
),
);

View file

@ -7,7 +7,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
@ -15,8 +16,10 @@ 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/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/progress_bar.dart';
@ -40,7 +43,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
late final FocusNode passwordFocusNode;
late final FocusNode passwordRepeatFocusNode;
late final StackFileSystem stackFileSystem;
late final SWBFileSystem stackFileSystem;
final zxcvbn = Zxcvbn();
String passwordFeedback =
@ -60,7 +63,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
@override
void initState() {
stackFileSystem = StackFileSystem();
stackFileSystem = SWBFileSystem();
fileLocationController = TextEditingController();
passwordController = TextEditingController();
passwordRepeatController = TextEditingController();
@ -101,41 +104,44 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
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(
"Create backup",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Create backup",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: child,
),
),
child: IntrinsicHeight(
child: child,
),
),
);
},
);
},
),
),
),
);
@ -443,228 +449,342 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
),
if (!isDesktop) const Spacer(),
!isDesktop
? TextButton(
style: shouldEnableCreate
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(context),
onPressed: !shouldEnableCreate
? null
: () async {
final String pathToSave =
fileLocationController.text;
final String passphrase = passwordController.text;
final String repeatPassphrase =
passwordRepeatController.text;
? Consumer(builder: (context, ref, __) {
return TextButton(
style: shouldEnableCreate
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(context),
onPressed: !shouldEnableCreate
? null
: () async {
final String pathToSave =
fileLocationController.text;
final String passphrase = passwordController.text;
final String repeatPassphrase =
passwordRepeatController.text;
if (pathToSave.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory not chosen",
context: context,
));
return;
}
if (!(await Directory(pathToSave).exists())) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory does not exist",
context: context,
));
return;
}
if (passphrase.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "A passphrase is required",
context: context,
));
return;
}
if (passphrase != repeatPassphrase) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Passphrase does not match",
context: context,
));
return;
}
unawaited(showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackDialog(
title: "Encrypting backup",
message: "This shouldn't take long",
),
));
// make sure the dialog is able to be displayed for at least 1 second
await Future<void>.delayed(
const Duration(seconds: 1));
final DateTime now = DateTime.now();
final String fileToSave =
"$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb";
final backup = await SWB.createStackWalletJSON();
bool result =
await SWB.encryptStackWalletWithPassphrase(
fileToSave,
passphrase,
jsonEncode(backup),
);
if (mounted) {
// pop encryption progress dialog
Navigator.of(context).pop();
if (result) {
await showDialog<dynamic>(
if (pathToSave.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory not chosen",
context: context,
barrierDismissible: false,
builder: (_) => Platform.isAndroid
? StackOkDialog(
title: "Backup saved to:",
message: fileToSave,
)
: const StackOkDialog(
title: "Backup creation succeeded"),
);
passwordController.text = "";
passwordRepeatController.text = "";
setState(() {});
} else {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackOkDialog(
title: "Backup creation failed"),
);
));
return;
}
}
},
child: Text(
"Create backup",
style: STextStyles.button(context),
),
)
if (!(await Directory(pathToSave).exists())) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory does not exist",
context: context,
));
return;
}
if (passphrase.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "A passphrase is required",
context: context,
));
return;
}
if (passphrase != repeatPassphrase) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Passphrase does not match",
context: context,
));
return;
}
unawaited(showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackDialog(
title: "Encrypting backup",
message: "This shouldn't take long",
),
));
// make sure the dialog is able to be displayed for at least 1 second
await Future<void>.delayed(
const Duration(seconds: 1));
final DateTime now = DateTime.now();
final String fileToSave =
"$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb";
final backup = await SWB.createStackWalletJSON(
secureStorage: ref.read(secureStoreProvider));
bool result =
await SWB.encryptStackWalletWithPassphrase(
fileToSave,
passphrase,
jsonEncode(backup),
);
if (mounted) {
// pop encryption progress dialog
if (!isDesktop) Navigator.of(context).pop();
if (result) {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => Platform.isAndroid
? StackOkDialog(
title: "Backup saved to:",
message: fileToSave,
)
: const StackOkDialog(
title: "Backup creation succeeded"),
);
passwordController.text = "";
passwordRepeatController.text = "";
setState(() {});
} else {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackOkDialog(
title: "Backup creation failed"),
);
}
}
},
child: Text(
"Create backup",
style: STextStyles.button(context),
),
);
})
: Row(
children: [
PrimaryButton(
width: 183,
desktopMed: true,
label: "Create backup",
enabled: shouldEnableCreate,
onPressed: !shouldEnableCreate
? null
: () async {
final String pathToSave =
fileLocationController.text;
final String passphrase =
passwordController.text;
final String repeatPassphrase =
passwordRepeatController.text;
Consumer(builder: (context, ref, __) {
return PrimaryButton(
width: 183,
buttonHeight: ButtonHeight.m,
label: "Create backup",
enabled: shouldEnableCreate,
onPressed: !shouldEnableCreate
? null
: () async {
final String pathToSave =
fileLocationController.text;
final String passphrase =
passwordController.text;
final String repeatPassphrase =
passwordRepeatController.text;
if (pathToSave.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory not chosen",
context: context,
));
return;
}
if (!(await Directory(pathToSave).exists())) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory does not exist",
context: context,
));
return;
}
if (passphrase.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "A passphrase is required",
context: context,
));
return;
}
if (passphrase != repeatPassphrase) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Passphrase does not match",
context: context,
));
return;
}
unawaited(showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackDialog(
title: "Encrypting backup",
message: "This shouldn't take long",
),
));
// make sure the dialog is able to be displayed for at least 1 second
await Future<void>.delayed(
const Duration(seconds: 1));
final DateTime now = DateTime.now();
final String fileToSave =
"$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb";
final backup =
await SWB.createStackWalletJSON();
bool result =
await SWB.encryptStackWalletWithPassphrase(
fileToSave,
passphrase,
jsonEncode(backup),
);
if (mounted) {
// pop encryption progress dialog
Navigator.of(context).pop();
if (result) {
await showDialog<dynamic>(
if (pathToSave.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory not chosen",
context: context,
barrierDismissible: false,
builder: (_) => Platform.isAndroid
? StackOkDialog(
title: "Backup saved to:",
message: fileToSave,
)
: const StackOkDialog(
title:
"Backup creation succeeded"),
);
passwordController.text = "";
passwordRepeatController.text = "";
setState(() {});
} else {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackOkDialog(
title: "Backup creation failed"),
);
));
return;
}
}
},
),
if (!(await Directory(pathToSave).exists())) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Directory does not exist",
context: context,
));
return;
}
if (passphrase.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "A passphrase is required",
context: context,
));
return;
}
if (passphrase != repeatPassphrase) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Passphrase does not match",
context: context,
));
return;
}
unawaited(
showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) {
if (Util.isDesktop) {
return DesktopDialog(
maxHeight: double.infinity,
maxWidth: 450,
child: Padding(
padding: const EdgeInsets.all(
32,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"Encrypting initial backup",
style:
STextStyles.desktopH3(
context),
),
const SizedBox(
height: 40,
),
Text(
"This shouldn't take long",
style: STextStyles
.desktopTextExtraExtraSmall(
context),
),
],
),
),
);
} else {
return const StackDialog(
title: "Encrypting initial backup",
message: "This shouldn't take long",
);
}
},
),
);
await Future<void>.delayed(
const Duration(seconds: 1));
// make sure the dialog is able to be displayed for at least 1 second
final fut = Future<void>.delayed(
const Duration(seconds: 1));
final DateTime now = DateTime.now();
final String fileToSave =
"$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb";
final backup =
await SWB.createStackWalletJSON(
secureStorage:
ref.read(secureStoreProvider));
bool result = await SWB
.encryptStackWalletWithPassphrase(
fileToSave,
passphrase,
jsonEncode(backup),
);
await Future.wait([fut]);
if (mounted) {
// pop encryption progress dialog
if (!isDesktop) Navigator.of(context).pop();
if (result) {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (context) {
if (Platform.isAndroid) {
return StackOkDialog(
title: "Backup saved to:",
message: fileToSave,
);
} else if (isDesktop) {
return DesktopDialog(
maxHeight: double.infinity,
maxWidth: 500,
child: Padding(
padding:
const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: Column(
mainAxisSize:
MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
const SizedBox(
height: 26),
Text(
"Stack backup saved to: \n",
style: STextStyles
.desktopH3(context),
),
Text(
fileToSave,
style: STextStyles
.desktopTextExtraExtraSmall(
context),
),
const SizedBox(
height: 40,
),
Row(
children: [
// const Spacer(),
Expanded(
child:
PrimaryButton(
label: "Ok",
buttonHeight:
ButtonHeight
.l,
onPressed: () {
int count = 0;
Navigator.of(
context)
.popUntil((_) =>
count++ >=
2);
},
),
),
],
)
],
),
),
);
} else {
return const StackOkDialog(
title:
"Backup creation succeeded");
}
});
passwordController.text = "";
passwordRepeatController.text = "";
setState(() {});
} else {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackOkDialog(
title: "Backup creation failed"),
);
}
}
},
);
}),
const SizedBox(
width: 16,
),
SecondaryButton(
width: 183,
desktopMed: true,
buttonHeight: ButtonHeight.m,
label: "Cancel",
onPressed: () {},
),

View file

@ -1,6 +1,11 @@
import 'package:flutter/material.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/desktop/desktop_dialog.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/stack_dialog.dart';
class CancelStackRestoreDialog extends StatelessWidget {
@ -14,38 +19,95 @@ class CancelStackRestoreDialog extends StatelessWidget {
onWillPop: () async {
return false;
},
child: StackDialog(
title: "Cancel restore process",
message:
"Cancelling will revert any changes that may have been applied",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Back",
style: STextStyles.itemSubtitle12(context),
),
onPressed: () {
Navigator.of(context).pop(false);
},
),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Yes, cancel",
style: STextStyles.itemSubtitle12(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.buttonTextPrimary,
child: !Util.isDesktop
? StackDialog(
title: "Cancel restore process",
message:
"Cancelling will revert any changes that may have been applied",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Text(
"Back",
style: STextStyles.itemSubtitle12(context),
),
onPressed: () {
Navigator.of(context).pop(false);
},
),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
child: Text(
"Yes, cancel",
style: STextStyles.itemSubtitle12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimary,
),
),
onPressed: () {
Navigator.of(context).pop(true);
},
),
)
: DesktopDialog(
maxHeight: 250,
maxWidth: 600,
child: Padding(
padding: const EdgeInsets.only(
top: 20, left: 32, right: 32, bottom: 20),
child: Column(
children: [
Text(
"Cancel Restore Process",
style: STextStyles.desktopH3(context),
),
const SizedBox(height: 24),
SizedBox(
width: 500,
child: RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarBackError,
child: Text(
"If you cancel, the restore will not complete, and "
"the wallets will not appear in your Stack.",
style: STextStyles.desktopTextMedium(context),
),
),
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SecondaryButton(
width: 248,
buttonHeight: ButtonHeight.l,
enabled: true,
label: "Keep restoring",
onPressed: () {
Navigator.of(context).pop(false);
},
),
const SizedBox(width: 20),
PrimaryButton(
width: 248,
buttonHeight: ButtonHeight.l,
enabled: true,
label: "Cancel anyway",
onPressed: () {
Navigator.of(context).pop(true);
},
)
],
),
],
),
),
),
),
onPressed: () {
Navigator.of(context).pop(true);
},
),
),
);
}
}

View file

@ -3,10 +3,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stack_wallet_backup/stack_wallet_backup.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/models/contact.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
@ -91,10 +88,13 @@ abstract class SWB {
static bool _shouldCancelRestore = false;
static bool _checkShouldCancel(PreRestoreState? revertToState) {
static bool _checkShouldCancel(
PreRestoreState? revertToState,
SecureStorageInterface secureStorageInterface,
) {
if (_shouldCancelRestore) {
if (revertToState != null) {
_revert(revertToState);
_revert(revertToState, secureStorageInterface);
} else {
_cancelCompleter!.complete();
_shouldCancelRestore = false;
@ -193,15 +193,15 @@ abstract class SWB {
/// [secureStorage] parameter exposed for testing purposes
static Future<Map<String, dynamic>> createStackWalletJSON({
FlutterSecureStorageInterface? secureStorage,
required SecureStorageInterface secureStorage,
}) async {
Logging.instance
.log("Starting createStackWalletJSON...", level: LogLevel.Info);
final _wallets = Wallets.sharedInstance;
Map<String, dynamic> backupJson = {};
NodeService nodeService = NodeService();
final _secureStore =
secureStorage ?? const SecureStorageWrapper(FlutterSecureStorage());
NodeService nodeService =
NodeService(secureStorageInterface: secureStorage);
final _secureStore = secureStorage;
Logging.instance.log("createStackWalletJSON awaiting DB.instance.mutex...",
level: LogLevel.Info);
@ -448,6 +448,7 @@ abstract class SWB {
Map<String, dynamic> validJSON,
StackRestoringUIState? uiState,
Map<String, String> oldToNewWalletIdMap,
SecureStorageInterface secureStorageInterface,
) async {
Map<String, dynamic> prefs = validJSON["prefs"] as Map<String, dynamic>;
List<dynamic>? addressBookEntries =
@ -486,7 +487,11 @@ abstract class SWB {
"SWB restoring nodes",
level: LogLevel.Warning,
);
await _restoreNodes(nodes, primaryNodes);
await _restoreNodes(
nodes,
primaryNodes,
secureStorageInterface,
);
uiState?.nodes = StackRestoringStatus.success;
uiState?.trades = StackRestoringStatus.restoring;
@ -543,6 +548,7 @@ abstract class SWB {
static Future<bool?> restoreStackWalletJSON(
String jsonBackup,
StackRestoringUIState? uiState,
SecureStorageInterface secureStorageInterface,
) async {
if (!Platform.isLinux) await Wakelock.enable();
@ -550,7 +556,8 @@ abstract class SWB {
"SWB creating temp backup",
level: LogLevel.Warning,
);
final preRestoreJSON = await createStackWalletJSON();
final preRestoreJSON =
await createStackWalletJSON(secureStorage: secureStorageInterface);
Logging.instance.log(
"SWB temp backup created",
level: LogLevel.Warning,
@ -587,19 +594,34 @@ abstract class SWB {
// basic cancel check here
// no reverting required yet as nothing has been written to store
if (_checkShouldCancel(null)) {
if (_checkShouldCancel(
null,
secureStorageInterface,
)) {
return false;
}
await _restoreEverythingButWallets(validJSON, uiState, oldToNewWalletIdMap);
await _restoreEverythingButWallets(
validJSON,
uiState,
oldToNewWalletIdMap,
secureStorageInterface,
);
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
final nodeService = NodeService();
final walletsService = WalletsService();
final nodeService = NodeService(
secureStorageInterface: secureStorageInterface,
);
final walletsService = WalletsService(
secureStorageInterface: secureStorageInterface,
);
final _prefs = Prefs.instance;
await _prefs.init();
@ -609,7 +631,10 @@ abstract class SWB {
for (var walletbackup in wallets) {
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
@ -647,7 +672,10 @@ abstract class SWB {
final failovers = nodeService.failoverNodesFor(coin: coin);
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
@ -655,6 +683,7 @@ abstract class SWB {
coin,
walletId,
walletName,
secureStorageInterface,
node,
txTracker,
_prefs,
@ -665,7 +694,10 @@ abstract class SWB {
managers.add(Tuple2(walletbackup, manager));
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
@ -679,7 +711,10 @@ abstract class SWB {
}
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
@ -690,7 +725,10 @@ abstract class SWB {
// start restoring wallets
for (final tuple in managers) {
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
final bools = await asyncRestore(tuple, uiState, walletsService);
@ -698,13 +736,19 @@ abstract class SWB {
}
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
for (Future<bool> status in restoreStatuses) {
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
await status;
@ -712,7 +756,10 @@ abstract class SWB {
if (!Platform.isLinux) await Wakelock.disable();
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
if (_checkShouldCancel(
preRestoreState,
secureStorageInterface,
)) {
return false;
}
@ -720,7 +767,10 @@ abstract class SWB {
return true;
}
static Future<void> _revert(PreRestoreState revertToState) async {
static Future<void> _revert(
PreRestoreState revertToState,
SecureStorageInterface secureStorageInterface,
) async {
Map<String, dynamic> prefs =
revertToState.validJSON["prefs"] as Map<String, dynamic>;
List<dynamic>? addressBookEntries =
@ -788,7 +838,9 @@ abstract class SWB {
}
// nodes
NodeService nodeService = NodeService();
NodeService nodeService = NodeService(
secureStorageInterface: secureStorageInterface,
);
final currentNodes = nodeService.nodes;
if (nodes == null) {
// no pre nodes found so we delete all but defaults
@ -914,7 +966,8 @@ abstract class SWB {
}
// finally remove any added wallets
final walletsService = WalletsService();
final walletsService =
WalletsService(secureStorageInterface: secureStorageInterface);
final namesData = await walletsService.walletNames;
for (final entry in namesData.entries) {
if (!revertToState.walletIds.contains(entry.value.walletId)) {
@ -989,8 +1042,11 @@ abstract class SWB {
static Future<void> _restoreNodes(
List<dynamic>? nodes,
List<dynamic>? primaryNodes,
SecureStorageInterface secureStorageInterface,
) async {
NodeService nodeService = NodeService();
NodeService nodeService = NodeService(
secureStorageInterface: secureStorageInterface,
);
if (nodes != null) {
for (var node in nodes) {
await nodeService.add(

View file

@ -4,15 +4,16 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:stackwallet/utilities/util.dart';
class StackFileSystem {
class SWBFileSystem {
Directory? rootPath;
Directory? startPath;
String? filePath;
String? dirPath;
final bool isDesktop = !(Platform.isAndroid || Platform.isIOS);
final bool isDesktop = Util.isDesktop;
Future<Directory> prepareStorage() async {
if (Platform.isAndroid) {
@ -25,11 +26,20 @@ class StackFileSystem {
}
debugPrint(rootPath!.absolute.toString());
Directory sampleFolder =
Directory('${rootPath!.path}Documents/Stack_backups');
late Directory sampleFolder;
if (Platform.isIOS) {
sampleFolder = Directory(rootPath!.path);
} else if (Platform.isAndroid) {
sampleFolder = Directory('${rootPath!.path}Documents/Stack_backups');
} else if (Platform.isLinux) {
sampleFolder = Directory('${rootPath!.path}/Stack_backups');
} else if (Platform.isWindows) {
sampleFolder = Directory('${rootPath!.path}/Stack_backups');
} else if (Platform.isMacOS) {
sampleFolder = Directory('${rootPath!.path}/Stack_backups');
}
try {
if (!sampleFolder.existsSync()) {
sampleFolder.createSync(recursive: true);

View file

@ -9,9 +9,9 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/assets.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/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
@ -62,204 +62,209 @@ class _RestoreFromEncryptedStringViewState
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
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) {
_onWillPop();
}
},
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) {
_onWillPop();
}
},
),
title: Text(
"Restore from file",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Restore from file",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("restoreFromFilePasswordFieldKey"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Enter password",
passwordFocusNode,
context,
).copyWith(
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
GestureDetector(
key: const Key(
"restoreFromFilePasswordFieldShowPasswordButtonKey"),
onTap: () async {
setState(() {
hidePassword = !hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("restoreFromFilePasswordFieldKey"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Enter password",
passwordFocusNode,
context,
).copyWith(
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
GestureDetector(
key: const Key(
"restoreFromFilePasswordFieldShowPasswordButtonKey"),
onTap: () async {
setState(() {
hidePassword = !hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
},
),
onChanged: (newValue) {
setState(() {});
},
),
),
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: passwordController.text.isEmpty
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(context),
onPressed: passwordController.text.isEmpty
? null
: () async {
final String passphrase =
passwordController.text;
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: passwordController.text.isEmpty
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(context),
onPressed: passwordController.text.isEmpty
? null
: () async {
final String passphrase =
passwordController.text;
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
bool shouldPop = false;
showDialog<dynamic>(
barrierDismissible: false,
context: context,
builder: (_) => WillPopScope(
onWillPop: () async {
return shouldPop;
},
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Material(
color: Colors.transparent,
child: Center(
child: Text(
"Decrypting Stack backup file",
style: STextStyles.pageTitleH2(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
bool shouldPop = false;
showDialog<dynamic>(
barrierDismissible: false,
context: context,
builder: (_) => WillPopScope(
onWillPop: () async {
return shouldPop;
},
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Material(
color: Colors.transparent,
child: Center(
child: Text(
"Decrypting Stack backup file",
style:
STextStyles.pageTitleH2(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.textWhite,
),
),
),
),
),
const SizedBox(
height: 64,
),
const Center(
child: LoadingIndicator(
width: 100,
const SizedBox(
height: 64,
),
),
],
),
),
);
final String? jsonString = await compute(
SWB.decryptStackWalletStringWithPassphrase,
Tuple2(widget.encrypted, passphrase),
debugLabel:
"stack wallet decryption compute",
);
if (mounted) {
// pop LoadingIndicator
shouldPop = true;
Navigator.of(context).pop();
passwordController.text = "";
if (jsonString == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message:
"Failed to decrypt backup file",
context: context,
);
return;
}
Navigator.of(context).push(
RouteGenerator.getRoute(
builder: (_) =>
StackRestoreProgressView(
jsonString: jsonString,
fromFile: true,
const Center(
child: LoadingIndicator(
width: 100,
),
),
],
),
),
);
}
},
child: Text(
"Restore",
style: STextStyles.button(context),
final String? jsonString = await compute(
SWB.decryptStackWalletStringWithPassphrase,
Tuple2(widget.encrypted, passphrase),
debugLabel:
"stack wallet decryption compute",
);
if (mounted) {
// pop LoadingIndicator
shouldPop = true;
Navigator.of(context).pop();
passwordController.text = "";
if (jsonString == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message:
"Failed to decrypt backup file",
context: context,
);
return;
}
Navigator.of(context).push(
RouteGenerator.getRoute(
builder: (_) =>
StackRestoreProgressView(
jsonString: jsonString,
fromFile: true,
),
),
);
}
},
child: Text(
"Restore",
style: STextStyles.button(context),
),
),
),
],
],
),
),
),
),
);
},
);
},
),
),
),
),

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