diff --git a/assets/images/glasses-hidden.png b/assets/images/glasses-hidden.png new file mode 100644 index 000000000..9176cc69b Binary files /dev/null and b/assets/images/glasses-hidden.png differ diff --git a/assets/images/glasses.png b/assets/images/glasses.png new file mode 100644 index 000000000..8c9e7dc27 Binary files /dev/null and b/assets/images/glasses.png differ diff --git a/assets/svg/dark/dark-theme.svg b/assets/svg/dark-theme.svg similarity index 100% rename from assets/svg/dark/dark-theme.svg rename to assets/svg/dark-theme.svg diff --git a/assets/svg/light/light-mode.svg b/assets/svg/light-mode.svg similarity index 100% rename from assets/svg/light/light-mode.svg rename to assets/svg/light-mode.svg diff --git a/assets/svg/lock-open.svg b/assets/svg/lock-open.svg new file mode 100644 index 000000000..f2b00f341 --- /dev/null +++ b/assets/svg/lock-open.svg @@ -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> diff --git a/assets/svg/ocean-breeze-theme.svg b/assets/svg/ocean-breeze-theme.svg new file mode 100644 index 000000000..0deb96ec8 --- /dev/null +++ b/assets/svg/ocean-breeze-theme.svg @@ -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> diff --git a/assets/svg/oceanBreeze/bell-new.svg b/assets/svg/oceanBreeze/bell-new.svg new file mode 100644 index 000000000..8cef32715 --- /dev/null +++ b/assets/svg/oceanBreeze/bell-new.svg @@ -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> diff --git a/assets/svg/oceanBreeze/buy-coins-icon.svg b/assets/svg/oceanBreeze/buy-coins-icon.svg new file mode 100644 index 000000000..d9613bccb --- /dev/null +++ b/assets/svg/oceanBreeze/buy-coins-icon.svg @@ -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> diff --git a/assets/svg/oceanBreeze/exchange-2.svg b/assets/svg/oceanBreeze/exchange-2.svg new file mode 100644 index 000000000..7baeaf87f --- /dev/null +++ b/assets/svg/oceanBreeze/exchange-2.svg @@ -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> diff --git a/assets/svg/oceanBreeze/stack-icon1.svg b/assets/svg/oceanBreeze/stack-icon1.svg new file mode 100644 index 000000000..f316012d7 --- /dev/null +++ b/assets/svg/oceanBreeze/stack-icon1.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg b/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg new file mode 100644 index 000000000..a54836bba --- /dev/null +++ b/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg b/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg new file mode 100644 index 000000000..5f9aa4256 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-exchange-icon.svg b/assets/svg/oceanBreeze/tx-exchange-icon.svg new file mode 100644 index 000000000..fcd3ef9dc --- /dev/null +++ b/assets/svg/oceanBreeze/tx-exchange-icon.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-icon-receive-failed.svg b/assets/svg/oceanBreeze/tx-icon-receive-failed.svg new file mode 100644 index 000000000..189bd15c9 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-receive-failed.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-icon-receive-pending.svg b/assets/svg/oceanBreeze/tx-icon-receive-pending.svg new file mode 100644 index 000000000..64ea8da3d --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-receive-pending.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-icon-receive.svg b/assets/svg/oceanBreeze/tx-icon-receive.svg new file mode 100644 index 000000000..1076d8d57 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-receive.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-icon-send-failed.svg b/assets/svg/oceanBreeze/tx-icon-send-failed.svg new file mode 100644 index 000000000..9751b61e8 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-send-failed.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-icon-send-pending.svg b/assets/svg/oceanBreeze/tx-icon-send-pending.svg new file mode 100644 index 000000000..e4ec777e3 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-send-pending.svg @@ -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> diff --git a/assets/svg/oceanBreeze/tx-icon-send.svg b/assets/svg/oceanBreeze/tx-icon-send.svg new file mode 100644 index 000000000..ee32aa6b4 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-send.svg @@ -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> diff --git a/lib/main.dart b/lib/main.dart index b1f917f58..728152951 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -57,6 +57,7 @@ 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'; @@ -76,7 +77,7 @@ void main() async { if (Util.isDesktop) { setWindowTitle('Stack Wallet'); - setWindowMinSize(const Size(1200, 1100)); + setWindowMinSize(const Size(1220, 900)); setWindowMaxSize(Size.infinite); } @@ -301,6 +302,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme> case "dark": themeType = ThemeType.dark; break; + case "oceanBreeze": + themeType = ThemeType.oceanBreeze; + break; case "light": default: themeType = ThemeType.light; @@ -314,8 +318,11 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme> WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - themeType == ThemeType.dark ? DarkColors() : LightColors()); + StackColors.fromStackColorTheme(themeType == ThemeType.dark + ? DarkColors() + : (themeType == ThemeType.light + ? LightColors() + : OceanBreezeColors())); if (Platform.isAndroid) { // fetch open file if it exists diff --git a/lib/notifications/show_flush_bar.dart b/lib/notifications/show_flush_bar.dart index 5320c8a9d..47cea682a 100644 --- a/lib/notifications/show_flush_bar.dart +++ b/lib/notifications/show_flush_bar.dart @@ -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, diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart index d89d42bbf..38181b9e1 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart @@ -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; } diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index 50e51110b..c87906870 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -18,17 +18,21 @@ import 'package:stackwallet/widgets/address_book_card.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; class AddressBookView extends ConsumerStatefulWidget { - const AddressBookView({Key? key, this.coin}) : super(key: key); + const AddressBookView({ + Key? key, + this.coin, + this.filterTerm, + }) : super(key: key); static const String routeName = "/addressBook"; final Coin? coin; + final String? filterTerm; @override ConsumerState<AddressBookView> createState() => _AddressBookViewState(); @@ -39,9 +43,6 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> { final _searchFocusNode = FocusNode(); - List<Contact>? _cache; - List<Contact>? _cacheFav; - String _searchTerm = ""; @override @@ -50,8 +51,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 +59,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,8 +101,8 @@ 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)); final isDesktop = Util.isDesktop; return ConditionalParent( @@ -199,7 +200,12 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> { child: IntrinsicHeight( child: Padding( padding: const EdgeInsets.all(4), - child: child, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height - 271, + ), + child: child, + ), ), ), ), @@ -209,196 +215,157 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> { ), ); }, - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: MediaQuery.of(context).size.height - 271, - ), - 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, - ), + 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 = ""; - }); - }, - ), - ], - ), - ), - ) - : null, ), - ) - : null, - ), - if (!isDesktop) 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: EdgeInsets.all(!isDesktop ? 0 : 15), - 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, + 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 = ""; + }); + }, + ), + ], ), ), - ], - ), - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "Your favorite contacts will appear here", - style: STextStyles.itemSubtitle(context), + ) + : 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, ), ), - ); - } - } - }, + ], + ), ), - const SizedBox( - height: 16, + if (contacts.isEmpty) + RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), ), - 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 Column( + 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: [ - RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Padding( - padding: const EdgeInsets.all(8.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( - "desktopContactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], + ...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, + ), ), - ), - ), ], - ); - } 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), + ), + ), + ), + ], ), ); } diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index 5835c80cd..2759e9cb1 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -21,6 +21,8 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/emoji_select_sheet.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -191,33 +193,6 @@ class _AddAddressBookEntryViewState style: STextStyles.desktopH3(context), textAlign: TextAlign.center, ), - const SizedBox(width: 10), - AppBarIconButton( - key: - const Key("addAddressBookEntryFavoriteButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context) - .extension<StackColors>()! - .background, - icon: SvgPicture.asset( - Assets.svg.star, - color: _isFavorite - ? Theme.of(context) - .extension<StackColors>()! - .favoriteStarActive - : Theme.of(context) - .extension<StackColors>()! - .favoriteStarInactive, - width: 20, - height: 20, - ), - onPressed: () { - setState(() { - _isFavorite = !_isFavorite; - }); - }, - ), ], ), ), @@ -225,10 +200,15 @@ class _AddAddressBookEntryViewState ], ), Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: child, - )), + child: Padding( + padding: const EdgeInsets.only( + left: 10, + right: 10, + bottom: 32, + ), + child: child, + ), + ), ], ); }, @@ -238,16 +218,17 @@ class _AddAddressBookEntryViewState padding: const EdgeInsets.symmetric(horizontal: 12), child: SingleChildScrollView( controller: scrollController, - padding: const EdgeInsets.only( + padding: EdgeInsets.only( // top: 8, left: 4, right: 4, - bottom: 16, + bottom: isDesktop ? 0 : 16, ), child: ConstrainedBox( constraints: BoxConstraints( // subtract top and bottom padding set in parent - minHeight: constraint.maxHeight - 16, // - 8, + minHeight: + constraint.maxHeight - (isDesktop ? 0 : 16), // - 8, ), child: IntrinsicHeight( child: Column( @@ -258,174 +239,111 @@ class _AddAddressBookEntryViewState mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - GestureDetector( - onTap: () { - if (_selectedEmoji != null) { - setState(() { - _selectedEmoji = null; - }); - return; - } + SizedBox( + height: 56, + width: 56, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + if (_selectedEmoji != null) { + setState(() { + _selectedEmoji = null; + }); + return; + } - ///TODO if desktop make dialog - !isDesktop - ? 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; - }); - } - }) - : showDialog<dynamic>( + showDialog<dynamic>( context: context, builder: (context) { - return DesktopDialog( + return const DesktopDialog( maxHeight: 700, - maxWidth: 700, - child: Column( - children: [ - Row( - children: [ - Padding( - padding: - const EdgeInsets - .all(32), - child: Text( - "Select emoji", - style: STextStyles - .desktopH3( - context), - textAlign: - TextAlign - .center, - ), - ), - ], - ), - Expanded( - child: LayoutBuilder( - builder: (context, - constraints) { - return SingleChildScrollView( - scrollDirection: - Axis.vertical, - child: - ConstrainedBox( - constraints: - BoxConstraints( - minHeight: - constraints - .maxHeight, - minWidth: - constraints - .maxWidth, - ), - child: - IntrinsicHeight( - child: Column( - children: const [ - Padding( - padding: - EdgeInsets.symmetric(horizontal: 32), - // child: - // EmojiSelectSheet(), - ), - ], - ), - ), - ), - ); - }, - ), - ), - ], + maxWidth: 600, + child: Padding( + padding: EdgeInsets.only( + left: 32, + right: 20, + top: 32, + bottom: 32, + ), + child: EmojiSelectSheet(), ), ); }).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }); - }, - child: SizedBox( - height: 56, - width: 56, - child: Stack( - children: [ - Container( - height: 56, - width: 56, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(24), - color: Theme.of(context) - .extension<StackColors>()! - .textFieldActiveBG, - ), - child: Center( - child: _selectedEmoji == null - ? SvgPicture.asset( - Assets.svg.user, - height: 30, - width: 30, - ) - : Text( - _selectedEmoji!.char, - style: STextStyles - .pageTitleH1(context), - ), - ), - ), - Align( - alignment: Alignment.bottomRight, - child: Container( - height: 14, - width: 14, + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); + }, + child: Stack( + children: [ + Container( + height: 56, + width: 56, decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(14), - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark), + borderRadius: + BorderRadius.circular(100), + color: Theme.of(context) + .extension<StackColors>()! + .textFieldActiveBG, + ), child: Center( child: _selectedEmoji == null ? SvgPicture.asset( - Assets.svg.plus, - color: Theme.of(context) - .extension< - StackColors>()! - .textWhite, - width: 12, - height: 12, + Assets.svg.user, + height: 30, + width: 30, ) - : SvgPicture.asset( - Assets.svg.thickX, - color: Theme.of(context) - .extension< - StackColors>()! - .textWhite, - width: 8, - height: 8, + : 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, + ), + ), + ), + ) + ], + ), ), ), ), @@ -501,100 +419,23 @@ class _AddAddressBookEntryViewState return; } - ///TODO if desktop make dialog - !isDesktop - ? 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; - }); - } - }) - : showDialog<dynamic>( - context: context, - builder: (context) { - return DesktopDialog( - maxHeight: 700, - maxWidth: 700, - child: Column( - children: [ - Row( - children: [ - Padding( - padding: - const EdgeInsets - .all(32), - child: Text( - "Select emoji", - style: STextStyles - .desktopH3( - context), - textAlign: - TextAlign - .center, - ), - ), - ], - ), - Expanded( - child: LayoutBuilder( - builder: (context, - constraints) { - return SingleChildScrollView( - scrollDirection: - Axis.vertical, - child: - ConstrainedBox( - constraints: - BoxConstraints( - minHeight: - constraints - .maxHeight, - minWidth: - constraints - .maxWidth, - ), - child: - IntrinsicHeight( - child: Column( - children: const [ - Padding( - padding: - EdgeInsets.symmetric(horizontal: 32), - // child: - // EmojiSelectSheet(), - ), - ], - ), - ), - ), - ); - }, - ), - ), - ], - ), - ); - }).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }); + 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, @@ -718,7 +559,7 @@ class _AddAddressBookEntryViewState ), ], ), - if (!isDesktop) const SizedBox(height: 8), + const SizedBox(height: 8), if (forms.length <= 1) const SizedBox( height: 8, @@ -782,22 +623,16 @@ class _AddAddressBookEntryViewState 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), - ), + child: SecondaryButton( + label: "Cancel", + buttonHeight: isDesktop ? ButtonHeight.m : null, onPressed: () async { - if (FocusScope.of(context).hasFocus) { + if (!isDesktop && + FocusScope.of(context).hasFocus) { FocusScope.of(context).unfocus(); await Future<void>.delayed( - const Duration(milliseconds: 75)); + const Duration(milliseconds: 75), + ); } if (mounted) { Navigator.of(context).pop(); @@ -824,16 +659,11 @@ class _AddAddressBookEntryViewState bool shouldEnableSave = validForms && nameExists; - return TextButton( - style: shouldEnableSave - ? Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor( - context) - : Theme.of(context) - .extension<StackColors>()! - .getPrimaryDisabledButtonColor( - context), + return PrimaryButton( + label: "Save", + buttonHeight: + isDesktop ? ButtonHeight.m : null, + enabled: shouldEnableSave, onPressed: shouldEnableSave ? () async { if (FocusScope.of(context) @@ -875,19 +705,6 @@ class _AddAddressBookEntryViewState } } : null, - child: Text( - "Save", - style: - STextStyles.button(context).copyWith( - color: shouldEnableSave - ? Theme.of(context) - .extension<StackColors>()! - .buttonTextPrimary - : Theme.of(context) - .extension<StackColors>()! - .buttonTextPrimaryDisabled, - ), - ), ); }, ), diff --git a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart index e5dbaa7b9..dc25c3dc1 100644 --- a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart @@ -12,7 +12,11 @@ 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/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 +59,170 @@ 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(); - } - }, + final isDesktop = Util.isDesktop; + + 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( + "Add new address", + style: STextStyles.navBarTitle(context), + ), ), - 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: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - 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), - ), - ); - }, - ), - ), - ], - ) - ], + 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( + 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 + } + }, + ), + ), + ], + ) + ], ), ); } diff --git a/lib/pages/address_book_views/subviews/address_book_filter_view.dart b/lib/pages/address_book_views/subviews/address_book_filter_view.dart index df779331e..55c3d47ac 100644 --- a/lib/pages/address_book_views/subviews/address_book_filter_view.dart +++ b/lib/pages/address_book_views/subviews/address_book_filter_view.dart @@ -38,7 +38,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(); @@ -159,7 +159,7 @@ class _AddressBookFilterViewState extends ConsumerState<AddressBookFilterView> { children: [ SecondaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Cancel", onPressed: () { @@ -169,7 +169,7 @@ class _AddressBookFilterViewState extends ConsumerState<AddressBookFilterView> { // const SizedBox(width: 16), PrimaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Apply", onPressed: () { diff --git a/lib/pages/address_book_views/subviews/contact_details_view.dart b/lib/pages/address_book_views/subviews/contact_details_view.dart index c0c10b3b1..a48a535c6 100644 --- a/lib/pages/address_book_views/subviews/contact_details_view.dart +++ b/lib/pages/address_book_views/subviews/contact_details_view.dart @@ -469,7 +469,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> { ..._cachedTransactions.map( (e) => TransactionCard( key: Key( - "contactDetailsTransaction_${e.item2.txid}_cardKey"), + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), transaction: e.item2, walletId: e.item1, ), @@ -499,7 +499,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> { ..._cachedTransactions.map( (e) => TransactionCard( key: Key( - "contactDetailsTransaction_${e.item2.txid}_cardKey"), + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), transaction: e.item2, walletId: e.item1, ), diff --git a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart index 618a41982..f0143d39d 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart @@ -12,7 +12,11 @@ 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/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 +48,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 +99,181 @@ 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(); - } - }, + final bool isDesktop = Util.isDesktop; + + 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( + "Edit address", + style: STextStyles.navBarTitle(context), + ), ), - 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: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - 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), - ), - ); - }, - ), - ), - ], - ), - ], + 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( + 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, + ), + ), + ], + ), + ], ), ); } diff --git a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart index fff01eee3..a9b264b3c 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart @@ -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,17 @@ 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/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 +74,323 @@ 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) => 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(); + } + }, + ), + ), + ], + ) + ], ), ); } diff --git a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart index b6cf0aad4..25cff073b 100644 --- a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart +++ b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart @@ -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'; @@ -47,6 +49,8 @@ class _NewContactAddressEntryFormState late final FocusNode addressLabelFocusNode; late final FocusNode addressFocusNode; + List<Coin> coins = []; + @override void initState() { addressLabelController = TextEditingController() @@ -55,6 +59,7 @@ class _NewContactAddressEntryFormState ..text = ref.read(addressEntryDataProvider(widget.id)).address ?? ""; addressLabelFocusNode = FocusNode(); addressFocusNode = FocusNode(); + coins = [...Coin.values]; super.initState(); } @@ -70,86 +75,179 @@ 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, ), @@ -253,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 { diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index e99cf2df4..540067915 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -7,15 +7,23 @@ 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/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 +37,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 +47,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget { final String routeOnSuccessName; final Trade trade; final bool? shouldSendPublicFiroFunds; + final bool fromDesktopStep4; @override ConsumerState<ConfirmChangeNowSendView> createState() => @@ -52,14 +62,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 +105,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 +154,60 @@ class _ConfirmChangeNowSendViewState } } + Future<void> _confirmSend() async { + final dynamic unlocked; + + 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(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(), + ), + ], + ), + ), + ); + } 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 +221,503 @@ 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, - ), - 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: [ - 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), - ), - ), - ], + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + 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, + ), + 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: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: child, + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Text( + "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), + ).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), + ).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), + ).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), + ).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), + ).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, + ), + ], + ), ), ); } diff --git a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart index 80bdcda62..779d99306 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart @@ -8,6 +8,8 @@ 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/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 +18,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 +120,106 @@ 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 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 +232,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 +294,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, - ), - ), - ], - ), - ), - ], - ), + ), + ], ), ), - ); - }, - ), + ), + ); + }, ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart index e1c1addd2..eb7a99299 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart @@ -6,6 +6,8 @@ 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/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 +15,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 +76,109 @@ 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 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 +190,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 +248,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, - ), - ), - ], - ), - ), - ], - ), + ), + ], ), ), - ); - }, - ), + ), + ); + }, ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 7b04f90b3..921b35bf0 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; @@ -18,19 +17,30 @@ import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view. import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_options.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.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/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/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.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/loading_indicator.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/textfields/exchange_textfield.dart'; import 'package:tuple/tuple.dart'; class ExchangeForm extends ConsumerStatefulWidget { @@ -54,6 +64,7 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { late final TextEditingController _sendController; late final TextEditingController _receiveController; + final isDesktop = Util.isDesktop; final FocusNode _sendFocusNode = FocusNode(); final FocusNode _receiveFocusNode = FocusNode(); @@ -135,14 +146,27 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { .read(exchangeFormStateProvider) .updateMarket(market, true); } catch (e) { - unawaited(showDialog<dynamic>( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", + unawaited( + showDialog<dynamic>( + context: context, + builder: (_) { + if (isDesktop) { + return const SimpleDesktopDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } else { + return const StackDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } + }, ), - )); + ); + return; } }, @@ -225,14 +249,26 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { .read(exchangeFormStateProvider) .updateMarket(market, true); } catch (e) { - unawaited(showDialog<dynamic>( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", + unawaited( + showDialog<dynamic>( + context: context, + builder: (_) { + if (isDesktop) { + return const SimpleDesktopDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } else { + return const StackDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } + }, ), - )); + ); return; } }, @@ -320,7 +356,7 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { await ref.read(exchangeFormStateProvider).swap(); } if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } _swapLock = false; } @@ -375,13 +411,65 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { } }).toList(growable: false); - final result = await Navigator.of(context).push( - MaterialPageRoute<dynamic>( - builder: (_) => FloatingRateCurrencySelectionView( - currencies: tickers, - ), - ), - ); + final result = isDesktop + ? await showDialog<Currency?>( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Choose a coin to exchange", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: FloatingRateCurrencySelectionView( + currencies: tickers, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }) + : await Navigator.of(context).push( + MaterialPageRoute<dynamic>( + builder: (_) => FloatingRateCurrencySelectionView( + currencies: tickers, + ), + ), + ); if (mounted && result is Currency) { onSelected(result); @@ -455,15 +543,73 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { .toList(growable: false); } - final result = await Navigator.of(context).push( - MaterialPageRoute<dynamic>( - builder: (_) => FixedRateMarketPairCoinSelectionView( - markets: marketsThatPairWithExcludedTicker, - currencies: ref.read(availableChangeNowCurrenciesProvider).currencies, - isFrom: excludedTicker != fromTicker, - ), - ), - ); + final result = isDesktop + ? await showDialog<String?>( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Choose a coin to exchange", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: FixedRateMarketPairCoinSelectionView( + markets: marketsThatPairWithExcludedTicker, + currencies: ref + .read( + availableChangeNowCurrenciesProvider) + .currencies, + isFrom: excludedTicker != fromTicker, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }) + : await Navigator.of(context).push( + MaterialPageRoute<dynamic>( + builder: (_) => FixedRateMarketPairCoinSelectionView( + markets: marketsThatPairWithExcludedTicker, + currencies: + ref.read(availableChangeNowCurrenciesProvider).currencies, + isFrom: excludedTicker != fromTicker, + ), + ), + ); if (mounted && result is String) { onSelected(result); @@ -563,14 +709,14 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { ? "-" : ref.read(exchangeFormStateProvider).toAmountString; if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } return; } } } if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } if (!(fromTicker == "-" || toTicker == "-")) { unawaited( @@ -616,7 +762,7 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { true, ); if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } return; case SimpleSwapExchange.exchangeName: @@ -653,7 +799,7 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { ? "-" : ref.read(exchangeFormStateProvider).toAmountString; if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } return; } @@ -665,7 +811,7 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { } } if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } unawaited( showFloatingFlushBar( @@ -718,15 +864,27 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { } if (!isAvailable) { - unawaited(showDialog<dynamic>( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Selected trade pair unavailable", - message: - "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + unawaited( + showDialog<dynamic>( + context: context, + barrierDismissible: true, + builder: (_) { + if (isDesktop) { + return SimpleDesktopDialog( + title: "Selected trade pair unavailable", + message: + "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + ); + } else { + return StackDialog( + title: "Selected trade pair unavailable", + message: + "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + ); + } + }, ), - )); + ); return; } rate = @@ -740,37 +898,101 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { shouldCancel = await showDialog<bool?>( context: context, barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to update trade estimate", - message: - "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?", - leftButton: TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12(context), - ), - onPressed: () { - // notify return to cancel - Navigator.of(context).pop(true); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Attempt", - style: STextStyles.button(context), - ), - onPressed: () { - // continue and try to attempt trade - Navigator.of(context).pop(false); - }, - ), - ), + builder: (_) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 500, + maxHeight: 300, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Failed to update trade estimate", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + const Spacer(), + Text( + estimate.warningMessage!, + style: STextStyles.desktopTextSmall(context), + ), + const Spacer(), + Text( + "Do you want to attempt trade anyways?", + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: () => Navigator.of( + context, + rootNavigator: true, + ).pop(true), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Attempt", + buttonHeight: ButtonHeight.l, + onPressed: () => Navigator.of( + context, + rootNavigator: true, + ).pop(false), + ), + ), + ], + ) + ], + ), + ); + } else { + return StackDialog( + title: "Failed to update trade estimate", + message: + "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?", + leftButton: TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + // notify return to cancel + Navigator.of(context).pop(true); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Attempt", + style: STextStyles.button(context), + ), + onPressed: () { + // continue and try to attempt trade + Navigator.of(context).pop(false); + }, + ), + ); + } + }, ); } @@ -799,20 +1021,61 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { if (walletInitiated) { ref.read(exchangeSendFromWalletIdStateProvider.state).state = Tuple2(walletId!, coin!); - unawaited( - Navigator.of(context).pushNamed( - Step2View.routeName, - arguments: model, - ), - ); + if (isDesktop) { + await showDialog<void>( + context: context, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 2, + model: model, + body: DesktopStep2( + model: model, + ), + ), + ); + }, + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + Step2View.routeName, + arguments: model, + ), + ); + } } else { ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; - unawaited( - Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, - ), - ); + + if (isDesktop) { + await showDialog<void>( + context: context, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 1, + model: model, + body: DesktopStep1( + model: model, + ), + ), + ); + }, + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + Step1View.routeName, + arguments: model, + ), + ); + } } } } @@ -960,220 +1223,106 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { color: Theme.of(context).extension<StackColors>()!.textDark3, ), ), - const SizedBox( - height: 4, + SizedBox( + height: isDesktop ? 10 : 4, ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( + ExchangeTextField( + controller: _sendController, + focusNode: _sendFocusNode, + textStyle: STextStyles.smallMed14(context).copyWith( color: Theme.of(context).extension<StackColors>()!.textDark, ), - focusNode: _sendFocusNode, - controller: _sendController, - textAlign: TextAlign.right, + buttonColor: + Theme.of(context).extension<StackColors>()!.buttonBackSecondary, + borderRadius: Constants.size.circularBorderRadius, + background: + Theme.of(context).extension<StackColors>()!.textFieldDefaultBG, onTap: () { if (_sendController.text == "-") { _sendController.text = ""; } }, onChanged: sendFieldOnChanged, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: selectSendCurrency, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - final image = _fetchIconUrlFromTicker(ref.watch( - exchangeFormStateProvider - .select((value) => value.fromTicker))); - - if (image != null && image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension<StackColors>()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular( - 18, - ), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - 18, - ), - child: const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension<StackColors>()!.accentColorDark - borderRadius: BorderRadius.circular(18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension<StackColors>()! - .textFieldDefaultBG, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - ref.watch(exchangeFormStateProvider.select((value) => - value.fromTicker?.toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .textDark, - ), - ), - if (!isWalletCoin(coin, true)) - const SizedBox( - width: 6, - ), - if (!isWalletCoin(coin, true)) - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension<StackColors>()! - .textDark, - ), - ], - ), - ), - ), - ), - ), - ), + onButtonTap: selectSendCurrency, + isWalletCoin: isWalletCoin(coin, true), + image: _fetchIconUrlFromTicker(ref.watch( + exchangeFormStateProvider.select((value) => value.fromTicker))), + ticker: ref.watch( + exchangeFormStateProvider.select((value) => value.fromTicker)), ), - const SizedBox( - height: 4, + SizedBox( + height: isDesktop ? 10 : 4, ), - Stack( + SizedBox( + height: isDesktop ? 10 : 4, + ), + if (ref + .watch( + exchangeFormStateProvider.select((value) => value.warning)) + .isNotEmpty && + !ref.watch( + exchangeFormStateProvider.select((value) => value.reversed))) + Text( + ref.watch( + exchangeFormStateProvider.select((value) => value.warning)), + style: STextStyles.errorSmall(context), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Positioned.fill( - child: Align( - alignment: Alignment.bottomLeft, - child: Text( - "You will receive", - style: STextStyles.itemSubtitle(context).copyWith( - color: - Theme.of(context).extension<StackColors>()!.textDark3, - ), + Text( + "You will receive", + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context).extension<StackColors>()!.textDark3, + ), + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: RoundedContainer( + padding: const EdgeInsets.all(6), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + radiusMultiplier: 0.75, + child: child, ), ), - ), - Center( - child: Column( - children: [ - const SizedBox( - height: 6, + child: GestureDetector( + onTap: () async { + await _swap(); + }, + child: Padding( + padding: const EdgeInsets.all(4), + child: SvgPicture.asset( + Assets.svg.swap, + width: 20, + height: 20, + color: Theme.of(context) + .extension<StackColors>()! + .accentColorDark, ), - GestureDetector( - onTap: () async { - await _swap(); - }, - child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg.swap, - width: 20, - height: 20, - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark, - ), - ), - ), - const SizedBox( - height: 6, - ), - ], - ), - ), - Positioned.fill( - child: Align( - alignment: ref.watch(exchangeFormStateProvider - .select((value) => value.reversed)) - ? Alignment.bottomRight - : Alignment.topRight, - child: Text( - ref.watch(exchangeFormStateProvider - .select((value) => value.warning)), - style: STextStyles.errorSmall(context), ), ), ), ], ), - const SizedBox( - height: 4, + SizedBox( + height: isDesktop ? 10 : 4, ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context).extension<StackColors>()!.textDark, - ), + ExchangeTextField( focusNode: _receiveFocusNode, controller: _receiveController, - readOnly: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.estimated || - ref.watch(exchangeProvider).name == - SimpleSwapExchange.exchangeName, + textStyle: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context).extension<StackColors>()!.textDark, + ), + buttonColor: + Theme.of(context).extension<StackColors>()!.buttonBackSecondary, + borderRadius: Constants.size.circularBorderRadius, + background: + Theme.of(context).extension<StackColors>()!.textFieldDefaultBG, onTap: () { if (!(ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) && @@ -1182,138 +1331,39 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { } }, onChanged: receiveFieldOnChanged, - textAlign: TextAlign.right, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - child: GestureDetector( - onTap: selectReceiveCurrency, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - final image = _fetchIconUrlFromTicker(ref.watch( - exchangeFormStateProvider - .select((value) => value.toTicker))); - - if (image != null && image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension<StackColors>()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular(18), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - 18, - ), - child: const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension<StackColors>()!.accentColorDark - borderRadius: BorderRadius.circular(18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension<StackColors>()! - .textFieldDefaultBG, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - ref.watch(exchangeFormStateProvider.select( - (value) => value.toTicker?.toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .textDark, - ), - ), - if (!isWalletCoin(coin, false)) - const SizedBox( - width: 6, - ), - if (!isWalletCoin(coin, false)) - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension<StackColors>()! - .textDark, - ), - ], - ), - ), - ), - ), - ), - ), + onButtonTap: selectReceiveCurrency, + isWalletCoin: isWalletCoin(coin, true), + image: _fetchIconUrlFromTicker(ref.watch( + exchangeFormStateProvider.select((value) => value.toTicker))), + ticker: ref.watch( + exchangeFormStateProvider.select((value) => value.toTicker)), + readOnly: ref.watch(prefsChangeNotifierProvider + .select((value) => value.exchangeRateType)) == + ExchangeRateType.estimated || + ref.watch(exchangeProvider).name == + SimpleSwapExchange.exchangeName, ), - const SizedBox( - height: 12, + if (ref + .watch( + exchangeFormStateProvider.select((value) => value.warning)) + .isNotEmpty && + ref.watch( + exchangeFormStateProvider.select((value) => value.reversed))) + Text( + ref.watch( + exchangeFormStateProvider.select((value) => value.warning)), + style: STextStyles.errorSmall(context), + ), + SizedBox( + height: isDesktop ? 20 : 12, ), RateTypeToggle( onChanged: onRateTypeChanged, ), if (ref.read(exchangeFormStateProvider).fromAmount != null && ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) - const SizedBox( - height: 8, + SizedBox( + height: isDesktop ? 20 : 12, ), if (ref.read(exchangeFormStateProvider).fromAmount != null && ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) @@ -1328,10 +1378,11 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> { reversed: ref.watch( exchangeFormStateProvider.select((value) => value.reversed)), ), - const SizedBox( - height: 12, + SizedBox( + height: isDesktop ? 20 : 12, ), PrimaryButton( + buttonHeight: isDesktop ? ButtonHeight.l : null, enabled: ref.watch( exchangeFormStateProvider.select((value) => value.canExchange)), onPressed: ref.watch(exchangeFormStateProvider diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index 0921f68e0..a8b403dcf 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -18,7 +18,6 @@ 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/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index c87175955..7c5f5541c 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -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,12 @@ 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/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 +37,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 +47,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(); @@ -90,21 +98,68 @@ 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 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 +167,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 +193,7 @@ class _SendFromViewState extends ConsumerState<SendFromView> { amount: amount, address: address, trade: trade, + fromDesktopStep4: widget.fromDesktopStep4, ), ); }, @@ -149,12 +213,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(); @@ -178,12 +244,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 +306,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 +323,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 +422,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( @@ -418,10 +507,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( @@ -504,7 +599,13 @@ class _SendFromCardState extends ConsumerState<SendFromCard> { Constants.size.circularBorderRadius, ), ), - onPressed: () => _send(manager), + onPressed: () async { + if (mounted) { + unawaited( + _send(manager), + ); + } + }, child: child, ), child: Row( diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 2113e199c..4dd768403 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -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,403 @@ 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); + } + return Text( + "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( + value: rate, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale), + ), + 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 { - 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), + return Text( + "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( + value: rate, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale), + ), + 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, ), - 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, + ), + ), + ], + ), + ), + ], ), - ], + ), ), ), ), diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart index 31460c75f..31ee01ce2 100644 --- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart +++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart @@ -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(0), + 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(0), + 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, + ), + ), + ], + ), ), ), ), diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 602d588da..0b7f4b502 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -206,16 +206,59 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> { padding: const EdgeInsets.only( right: 12, ), - child: RoundedWhiteContainer( - borderColor: isDesktop - ? Theme.of(context).extension<StackColors>()!.background - : null, - padding: const EdgeInsets.all(0), - child: ListView( - primary: false, - shrinkWrap: true, - children: children, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RoundedWhiteContainer( + borderColor: + Theme.of(context).extension<StackColors>()!.background, + padding: const EdgeInsets.all(0), + child: ListView( + primary: false, + shrinkWrap: true, + children: children, + ), + ), + if (!hasTx && + isStackCoin(trade.payInCurrency) && + (trade.status == "New" || + trade.status == "new" || + trade.status == "waiting" || + trade.status == "Waiting")) + const SizedBox( + height: 32, + ), + if (!hasTx && + isStackCoin(trade.payInCurrency) && + (trade.status == "New" || + trade.status == "new" || + trade.status == "waiting" || + trade.status == "Waiting")) + SecondaryButton( + label: "Send from Stack", + buttonHeight: ButtonHeight.l, + onPressed: () { + final amount = sendAmount; + final address = trade.payInAddress; + + final coin = + coinFromTickerCaseInsensitive(trade.payInCurrency); + + Navigator.of(context).pushNamed( + SendFromView.routeName, + arguments: Tuple4( + coin, + amount, + address, + trade, + ), + ); + }, + ), + const SizedBox( + height: 32, + ), + ], ), ), ), @@ -350,33 +393,94 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> { padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), - color: Theme.of(context) - .extension<StackColors>()! - .warningBackground, - child: RichText( - text: TextSpan( - text: - "You must send at least ${sendAmount.toStringAsFixed( - trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.payInCurrency.toUpperCase()}. ", - style: STextStyles.label700(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .warningForeground, - ), - children: [ - TextSpan( - text: - "If you send less than ${sendAmount.toStringAsFixed( - trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", - style: STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .warningForeground, + color: isDesktop + ? Theme.of(context).extension<StackColors>()!.popupBG + : Theme.of(context) + .extension<StackColors>()! + .warningBackground, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Amount", + style: STextStyles.desktopTextExtraExtraSmall( + context), + ), + const SizedBox( + height: 2, + ), + Text( + "${trade.payInAmount} ${trade.payInCurrency.toUpperCase()}", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ), + ], ), - ), - ]), + IconCopyButton( + data: trade.payInAmount, + ), + ], + ), + const SizedBox( + height: 6, + ), + child, + ], + ), + child: RichText( + text: TextSpan( + text: + "You must send at least ${sendAmount.toStringAsFixed( + trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, + )} ${trade.payInCurrency.toUpperCase()}. ", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .accentColorRed) + : STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .warningForeground, + ), + children: [ + TextSpan( + text: + "If you send less than ${sendAmount.toStringAsFixed( + trade.payInCurrency.toLowerCase() == "xmr" + ? 12 + : 8, + )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .accentColorRed) + : STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .warningForeground, + ), + ), + ]), + ), ), ), if (sentFromStack) @@ -1035,12 +1139,13 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> { ], ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - if (isStackCoin(trade.payInCurrency) && + if (!isDesktop) + const SizedBox( + height: 12, + ), + if (!isDesktop && + !hasTx && + isStackCoin(trade.payInCurrency) && (trade.status == "New" || trade.status == "new" || trade.status == "waiting" || diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 981def830..05cedb148 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -530,7 +530,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> { }); } : onGeneratePressed, - desktopMed: true, + buttonHeight: ButtonHeight.l, ), if (isDesktop && didGenerate) Row( @@ -586,7 +586,6 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> { if (!isDesktop) SecondaryButton( width: 170, - desktopMed: true, onPressed: () async { await _capturePng(false); }, @@ -606,7 +605,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> { ), PrimaryButton( width: 170, - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { // TODO: add save functionality instead of share // save works on linux at the moment diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 0f1692c08..276203804 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -37,6 +37,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"; @@ -44,6 +45,7 @@ class ConfirmTransactionView extends ConsumerStatefulWidget { final Map<String, dynamic> transactionInfo; final String walletId; final String routeOnSuccessName; + final bool isTradeTransaction; @override ConsumerState<ConfirmTransactionView> createState() => @@ -148,7 +150,7 @@ class _ConfirmTransactionViewState const Spacer(), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Ok", onPressed: Navigator.of(context).pop, ), @@ -780,7 +782,7 @@ class _ConfirmTransactionViewState : const EdgeInsets.all(0), child: PrimaryButton( label: "Send", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { final dynamic unlocked; @@ -833,8 +835,19 @@ class _ConfirmTransactionViewState ); } - if (unlocked is bool && unlocked && mounted) { - unawaited(_attemptSend(context)); + if (mounted) { + if (unlocked == true) { + unawaited(_attemptSend(context)); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: Util.isDesktop + ? "Invalid passphrase" + : "Invalid PIN", + context: context), + ); + } } }, ), diff --git a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart index 045218e54..1f6c95df6 100644 --- a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart +++ b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart @@ -77,7 +77,7 @@ class _RestoringDialogState extends State<BuildingTransactionDialog> height: 40, ), SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { onCancel.call(); diff --git a/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart b/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart index e639a8cf8..d6de3c6ee 100644 --- a/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart @@ -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, ); diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart index b0cf35a84..d1e893802 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart @@ -8,6 +8,7 @@ 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/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; @@ -18,6 +19,17 @@ 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( @@ -100,65 +112,39 @@ class AppearanceSettingsView extends ConsumerWidget { 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, + 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( - "Enable dark mode", + "Choose Theme", 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 Padding( + padding: EdgeInsets.all(10), + child: ThemeOptionsView(), + ), ], ), - ), - ); - }, + ], + ), + ), ), ), ], @@ -172,3 +158,274 @@ class AppearanceSettingsView extends ConsumerWidget { ); } } + +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, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/currency_view.dart b/lib/pages/settings_views/global_settings_view/currency_view.dart index 4e8fd5f6e..dccf2d61b 100644 --- a/lib/pages/settings_views/global_settings_view/currency_view.dart +++ b/lib/pages/settings_views/global_settings_view/currency_view.dart @@ -189,7 +189,7 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -199,7 +199,7 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> { Expanded( child: PrimaryButton( label: "Save changes", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () { ref.read(prefsChangeNotifierProvider).currency = current; diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 890953caf..9062314f0 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -238,7 +238,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () => Navigator.of( context, rootNavigator: true, @@ -251,7 +251,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> { Expanded( child: PrimaryButton( label: "Save", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () => Navigator.of( context, rootNavigator: true, @@ -494,6 +494,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> { condition: isDesktop, builder: (child) => DesktopDialog( maxWidth: 580, + maxHeight: 500, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -561,7 +562,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> { child: SecondaryButton( label: "Test connection", enabled: testConnectionEnabled, - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: testConnectionEnabled ? () async { await _testConnection(); @@ -578,7 +579,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> { child: PrimaryButton( label: "Save", enabled: saveEnabled, - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: saveEnabled ? attemptSave : null, ), ), @@ -830,110 +831,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, diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index a80a64147..71d764135 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -349,7 +349,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); }, @@ -364,7 +364,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> { child: !nodeId.startsWith("default") ? PrimaryButton( label: _desktopReadOnly ? "Edit" : "Save", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { final shouldSave = _desktopReadOnly == false; setState(() { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 02556beb9..a6241d25a 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -562,7 +562,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> { Consumer(builder: (context, ref, __) { return PrimaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.m, label: "Create backup", enabled: shouldEnableCreate, onPressed: !shouldEnableCreate @@ -735,7 +735,9 @@ class _RestoreFromFileViewState extends State<CreateBackupView> { child: PrimaryButton( label: "Ok", - desktopMed: true, + buttonHeight: + ButtonHeight + .l, onPressed: () { int count = 0; Navigator.of( @@ -778,7 +780,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> { ), SecondaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.m, label: "Cancel", onPressed: () {}, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart index a9f4e134d..905cdea72 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart @@ -85,7 +85,7 @@ class CancelStackRestoreDialog extends StatelessWidget { children: [ SecondaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Keep restoring", onPressed: () { @@ -95,7 +95,7 @@ class CancelStackRestoreDialog extends StatelessWidget { const SizedBox(width: 20), PrimaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Cancel anyway", onPressed: () { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 76d280980..310be9f2b 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -754,7 +754,7 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -764,7 +764,7 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> { Expanded( child: PrimaryButton( label: "Save", - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: shouldEnableCreate, onPressed: onSavePressed, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index c5ccfa6b3..d6571967d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -389,7 +389,7 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> { children: [ PrimaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.m, label: "Restore", enabled: !(passwordController.text.isEmpty || fileLocationController.text.isEmpty), @@ -566,7 +566,7 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> { ), SecondaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.m, label: "Cancel", onPressed: () {}, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index e34def23d..c7f53378d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -108,7 +108,7 @@ class _StackRestoreProgressViewState // children: [ // SecondaryButton( // width: 248, - // desktopMed: true, + // buttonHeight: ButtonHeight.l, // enabled: true, // label: "Keep restoring", // onPressed: () { @@ -118,7 +118,7 @@ class _StackRestoreProgressViewState // const SizedBox(width: 16), // PrimaryButton( // width: 248, - // desktopMed: true, + // buttonHeight: ButtonHeight.l, // enabled: true, // label: "Cancel anyway", // onPressed: () { @@ -681,7 +681,7 @@ class _StackRestoreProgressViewState _success ? PrimaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Done", onPressed: () async { @@ -690,7 +690,7 @@ class _StackRestoreProgressViewState ) : SecondaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Cancel restore process", onPressed: () async { diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart index 950d8d79e..150af6ac5 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart @@ -58,7 +58,7 @@ class ConfirmFullRescanDialog extends StatelessWidget { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, label: "Cancel", ), @@ -68,7 +68,7 @@ class ConfirmFullRescanDialog extends StatelessWidget { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () { Navigator.of(context).pop(); onConfirm.call(); diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index accf244eb..3044467aa 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -77,7 +77,7 @@ class _WalletNetworkSettingsViewState late double _percent; late int _blocksRemaining; - bool _advancedIsExpanded = true; + bool _advancedIsExpanded = false; Future<void> _attemptRescan() async { if (!Platform.isLinux) await Wakelock.enable(); @@ -855,8 +855,8 @@ class _WalletNetworkSettingsViewState ), SvgPicture.asset( _advancedIsExpanded - ? Assets.svg.chevronDown - : Assets.svg.chevronUp, + ? Assets.svg.chevronUp + : Assets.svg.chevronDown, width: 12, height: 6, color: Theme.of(context) @@ -877,11 +877,11 @@ class _WalletNetworkSettingsViewState text: "Rescan", onTap: () async { await Navigator.of(context).push( - FadePageRoute<void>( + FadePageRoute<void>( ConfirmFullRescanDialog( onConfirm: _attemptRescan, ), - const RouteSettings(), + const RouteSettings(), ), ); // await showDialog<dynamic>( diff --git a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart index c9ff64393..74308f2e8 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart @@ -3,14 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.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/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; - class WalletBalanceToggleSheet extends ConsumerWidget { const WalletBalanceToggleSheet({ Key? key, @@ -153,7 +152,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) @@ -195,7 +194,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) @@ -287,7 +286,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) @@ -329,7 +328,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index d41877a9a..95dcc8126 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -385,7 +385,7 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> { ), if (isDesktop) SecondaryButton( - desktopMed: isDesktop, + buttonHeight: ButtonHeight.l, width: 200, label: "Filter", icon: SvgPicture.asset( diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 1c2fb8e5d..4d45428ff 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -30,6 +30,8 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/pencil_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -154,60 +156,142 @@ class _TransactionDetailsViewState Future<bool> showExplorerWarning(String explorer) async { final bool? shouldContinue = await showDialog<bool>( - context: context, - barrierDismissible: false, - builder: (_) => StackDialog( - title: "Attention", - message: - "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", - icon: Row( - children: [ - Consumer(builder: (_, ref, __) { - return Checkbox( - value: ref.watch(prefsChangeNotifierProvider - .select((value) => value.hideBlockExplorerWarning)), - onChanged: (value) { - if (value is bool) { - ref - .read(prefsChangeNotifierProvider) - .hideBlockExplorerWarning = value; - setState(() {}); - } + context: context, + barrierDismissible: false, + builder: (_) { + if (!isDesktop) { + return StackDialog( + title: "Attention", + message: + "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", + icon: Row( + children: [ + Consumer(builder: (_, ref, __) { + return Checkbox( + value: ref.watch(prefsChangeNotifierProvider + .select((value) => value.hideBlockExplorerWarning)), + onChanged: (value) { + if (value is bool) { + ref + .read(prefsChangeNotifierProvider) + .hideBlockExplorerWarning = value; + setState(() {}); + } + }, + ); + }), + Text( + "Never show again", + style: STextStyles.smallMed14(context), + ) + ], + ), + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(false); }, - ); - }), - Text( - "Never show again", - style: STextStyles.smallMed14(context), - ) - ], - ), - leftButton: TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .accentColorDark), + ), + ), + rightButton: TextButton( + style: Theme.of(context) .extension<StackColors>()! - .accentColorDark), - ), - ), - rightButton: TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - Navigator.of(context).pop(true); - }, - child: Text( - "Continue", - style: STextStyles.button(context), - ), - ), - ), - ); + .getPrimaryEnabledButtonColor(context), + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text( + "Continue", + style: STextStyles.button(context), + ), + ), + ); + } else { + return DesktopDialog( + maxWidth: 550, + maxHeight: 300, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Attention", + style: STextStyles.desktopH2(context), + ), + Row( + children: [ + Consumer(builder: (_, ref, __) { + return Checkbox( + value: ref.watch(prefsChangeNotifierProvider + .select((value) => + value.hideBlockExplorerWarning)), + onChanged: (value) { + if (value is bool) { + ref + .read(prefsChangeNotifierProvider) + .hideBlockExplorerWarning = value; + setState(() {}); + } + }, + ); + }), + Text( + "Never show again", + style: STextStyles.smallMed14(context), + ) + ], + ), + ], + ), + const SizedBox(height: 16), + Text( + "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 35), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(false); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(true); + }, + ), + ], + ), + ], + ), + ), + ); + } + }); return shouldContinue ?? false; } @@ -995,15 +1079,17 @@ class _TransactionDetailsViewState .externalApplication, ); } catch (_) { - unawaited(showDialog<void>( - context: context, - builder: (_) => StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", + unawaited( + showDialog<void>( + context: context, + builder: (_) => StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), ), - )); + ); } finally { // Future<void>.delayed( // const Duration(seconds: 1), diff --git a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart index abebb71e4..d135ea276 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart @@ -869,7 +869,7 @@ class _TransactionSearchViewState Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: isDesktop, + buttonHeight: ButtonHeight.l, onPressed: () async { if (!isDesktop) { if (FocusScope.of(context).hasFocus) { @@ -919,7 +919,7 @@ class _TransactionSearchViewState ), Expanded( child: PrimaryButton( - desktopMed: isDesktop, + buttonHeight: ButtonHeight.l, onPressed: () async { await _onApplyPressed(); }, diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart new file mode 100644 index 000000000..105c485f0 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopExchangeView extends StatefulWidget { + const DesktopExchangeView({Key? key}) : super(key: key); + + static const String routeName = "/desktopExchange"; + + @override + State<DesktopExchangeView> createState() => _DesktopExchangeViewState(); +} + +class _DesktopExchangeViewState extends State<DesktopExchangeView> { + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Padding( + padding: const EdgeInsets.only( + left: 24, + ), + child: Text( + "Exchange", + style: STextStyles.desktopH3(context), + ), + ), + ), + body: Padding( + padding: const EdgeInsets.only( + left: 24, + right: 24, + bottom: 24, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Exchange details", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 16, + ), + const RoundedWhiteContainer( + padding: EdgeInsets.all(24), + child: ExchangeForm(), + ), + ], + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Row( + children: const [ + Expanded( + child: DesktopTradeHistory(), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart new file mode 100644 index 000000000..8dbaf9580 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; + +class StepScaffold extends StatefulWidget { + const StepScaffold({ + Key? key, + required this.body, + required this.step, + required this.model, + }) : super(key: key); + + final Widget body; + final int step; + final IncompleteExchangeModel model; + + @override + State<StepScaffold> createState() => _StepScaffoldState(); +} + +class _StepScaffoldState extends State<StepScaffold> { + int currentStep = 0; + late final IncompleteExchangeModel model; + + @override + void initState() { + currentStep = widget.step; + model = widget.model; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + const AppBarBackButton( + isCompact: true, + iconSize: 23, + ), + Text( + "Exchange ${model.sendTicker.toUpperCase()} to ${model.receiveTicker.toUpperCase()}", + style: STextStyles.desktopH3(context), + ), + ], + ), + const SizedBox( + height: 12, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: DesktopExchangeStepsIndicator( + currentStep: currentStep, + ), + ), + const SizedBox( + height: 32, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: widget.body, + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart new file mode 100644 index 000000000..031dc0649 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.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_white_container.dart'; + +class DesktopStep1 extends ConsumerWidget { + const DesktopStep1({ + Key? key, + required this.model, + }) : super(key: key); + + final IncompleteExchangeModel model; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Column( + children: [ + Text( + "Confirm amount", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Network fees and other exchange charges are included in the rate.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension<StackColors>()!.background, + padding: const EdgeInsets.all(0), + child: Column( + children: [ + DesktopStepItem( + label: "Exchange", + value: ref.watch(currentExchangeNameStateProvider.state).state, + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: "You send", + value: + "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: "You receive", + value: + "~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}", + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: model.rateType == ExchangeRateType.estimated + ? "Estimated rate" + : "Fixed rate", + value: model.rateInfo, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Back", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Next", + buttonHeight: ButtonHeight.l, + onPressed: () async { + await showDialog<void>( + context: context, + barrierColor: Colors.transparent, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 2, + model: model, + body: DesktopStep2( + model: model, + ), + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart new file mode 100644 index 000000000..7b793210c --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -0,0 +1,620 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; +import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class DesktopStep2 extends ConsumerStatefulWidget { + const DesktopStep2({ + Key? key, + required this.model, + this.clipboard = const ClipboardWrapper(), + }) : super(key: key); + + final IncompleteExchangeModel model; + final ClipboardInterface clipboard; + + @override + ConsumerState<DesktopStep2> createState() => _DesktopStep2State(); +} + +class _DesktopStep2State extends ConsumerState<DesktopStep2> { + late final IncompleteExchangeModel model; + late final ClipboardInterface clipboard; + + late final TextEditingController _toController; + late final TextEditingController _refundController; + + late final FocusNode _toFocusNode; + late final FocusNode _refundFocusNode; + + bool enableNext = false; + + bool isStackCoin(String ticker) { + try { + coinFromTickerCaseInsensitive(ticker); + return true; + } on ArgumentError catch (_) { + return false; + } + } + + void selectRecipientAddressFromStack() async { + try { + final coin = coinFromTickerCaseInsensitive( + model.receiveTicker, + ); + + final address = await showDialog<String?>( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Padding( + padding: const EdgeInsets.all(32), + child: DesktopChooseFromStack( + coin: coin, + ), + ), + ), + ); + + if (address is String) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(address); + + _toController.text = manager.walletName; + model.recipientAddress = await manager.currentReceivingAddress; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + } + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); + } + + void selectRefundAddressFromStack() async { + try { + final coin = coinFromTickerCaseInsensitive( + model.sendTicker, + ); + + final address = await showDialog<String?>( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Padding( + padding: const EdgeInsets.all(32), + child: DesktopChooseFromStack( + coin: coin, + ), + ), + ), + ); + if (address is String) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(address); + + _refundController.text = manager.walletName; + model.refundAddress = await manager.currentReceivingAddress; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + } + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); + } + + void selectRecipientFromAddressBook() async { + final coin = coinFromTickerCaseInsensitive( + model.receiveTicker, + ); + + final entry = await showDialog<ContactAddressEntry?>( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, + ), + ), + ], + ), + ), + ); + + if (entry != null) { + _toController.text = entry.address; + model.recipientAddress = entry.address; + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); + } + } + + void selectRefundFromAddressBook() async { + final coin = coinFromTickerCaseInsensitive( + model.sendTicker, + ); + + final entry = await showDialog<ContactAddressEntry?>( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, + ), + ), + ], + ), + ), + ); + + if (entry != null) { + _refundController.text = entry.address; + model.refundAddress = entry.address; + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); + } + } + + @override + void initState() { + model = widget.model; + clipboard = widget.clipboard; + + _toController = TextEditingController(); + _refundController = TextEditingController(); + + _toFocusNode = FocusNode(); + _refundFocusNode = FocusNode(); + + final tuple = ref.read(exchangeSendFromWalletIdStateProvider.state).state; + if (tuple != null) { + if (model.receiveTicker.toLowerCase() == + tuple.item2.ticker.toLowerCase()) { + ref + .read(walletsChangeNotifierProvider) + .getManager(tuple.item1) + .currentReceivingAddress + .then((value) { + _toController.text = value; + model.recipientAddress = _toController.text; + }); + } else { + if (model.sendTicker.toUpperCase() == + tuple.item2.ticker.toUpperCase()) { + ref + .read(walletsChangeNotifierProvider) + .getManager(tuple.item1) + .currentReceivingAddress + .then((value) { + _refundController.text = value; + model.refundAddress = _refundController.text; + }); + } + } + } + + super.initState(); + } + + @override + void dispose() { + _toController.dispose(); + _refundController.dispose(); + + _toFocusNode.dispose(); + _refundFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Enter exchange details", + style: STextStyles.desktopTextMedium(context), + textAlign: TextAlign.center, + ), + const SizedBox( + height: 8, + ), + Text( + "Enter your recipient and refund addresses", + style: STextStyles.desktopTextExtraExtraSmall(context), + textAlign: TextAlign.center, + ), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Recipient Wallet", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconRight), + ), + if (isStackCoin(model.receiveTicker)) + BlueTextButton( + text: "Choose from stack", + onTap: selectRecipientAddressFromStack, + ), + ], + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + onTap: () {}, + key: const Key("recipientExchangeStep2ViewAddressFieldKey"), + controller: _toController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: <TextInputFormatter>[ + // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + focusNode: _toFocusNode, + style: STextStyles.field(context), + onChanged: (value) { + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); + }, + decoration: standardInputDecoration( + "Enter the ${model.receiveTicker.toUpperCase()} payout address", + _toFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _toController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _toController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + _toController.text = ""; + model.recipientAddress = _toController.text; + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = data.text!.trim(); + _toController.text = content; + model.recipientAddress = _toController.text; + setState(() { + enableNext = + _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); + } + }, + child: _toController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_toController.text.isEmpty && + isStackCoin(model.receiveTicker)) + TextFieldIconButton( + key: const Key("sendViewAddressBookButtonKey"), + onTap: selectRecipientFromAddressBook, + child: const AddressBookIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 10, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension<StackColors>()!.background, + child: Text( + "This is the wallet where your ${model.receiveTicker.toUpperCase()} will be sent to.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + const SizedBox( + height: 24, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Refund Wallet (required)", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconRight), + ), + if (isStackCoin(model.sendTicker)) + BlueTextButton( + text: "Choose from stack", + onTap: selectRefundAddressFromStack, + ), + ], + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("refundExchangeStep2ViewAddressFieldKey"), + controller: _refundController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: <TextInputFormatter>[ + // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + focusNode: _refundFocusNode, + style: STextStyles.field(context), + onChanged: (value) { + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); + }, + decoration: standardInputDecoration( + "Enter ${model.sendTicker.toUpperCase()} refund address", + _refundFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _refundController.text.isEmpty + ? const EdgeInsets.only(right: 16) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _refundController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + _refundController.text = ""; + model.refundAddress = _refundController.text; + + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = data.text!.trim(); + + _refundController.text = content; + model.refundAddress = _refundController.text; + + setState(() { + enableNext = + _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); + } + }, + child: _refundController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_refundController.text.isEmpty && + isStackCoin(model.sendTicker)) + TextFieldIconButton( + key: const Key("sendViewAddressBookButtonKey"), + onTap: selectRefundFromAddressBook, + child: const AddressBookIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 10, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension<StackColors>()!.background, + child: Text( + "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Back", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Next", + enabled: enableNext, + buttonHeight: ButtonHeight.l, + onPressed: () async { + await showDialog<void>( + context: context, + barrierColor: Colors.transparent, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 3, + model: model, + body: DesktopStep3( + model: model, + ), + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart new file mode 100644 index 000000000..284416545 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart @@ -0,0 +1,254 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; +import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart'; +import 'package:stackwallet/providers/exchange/exchange_provider.dart'; +import 'package:stackwallet/providers/global/trades_service_provider.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/notifications_api.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/custom_loading_overlay.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/desktop/simple_desktop_dialog.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopStep3 extends ConsumerStatefulWidget { + const DesktopStep3({ + Key? key, + required this.model, + }) : super(key: key); + + final IncompleteExchangeModel model; + + @override + ConsumerState<DesktopStep3> createState() => _DesktopStep3State(); +} + +class _DesktopStep3State extends ConsumerState<DesktopStep3> { + late final IncompleteExchangeModel model; + + Future<void> createTrade() 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, + ); + + if (response.value == null) { + if (mounted) { + Navigator.of(context).pop(); + } + + unawaited( + showDialog<void>( + context: context, + barrierDismissible: true, + builder: (_) => SimpleDesktopDialog( + 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 (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( + showDialog<void>( + context: context, + barrierColor: Colors.transparent, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 4, + model: model, + body: DesktopStep4( + model: model, + ), + ), + ); + }, + ), + ); + } + } + + @override + void initState() { + model = widget.model; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + "Confirm exchange details", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 20, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension<StackColors>()!.background, + padding: const EdgeInsets.all(0), + child: Column( + children: [ + DesktopStepItem( + label: "Exchange", + value: ref.watch(currentExchangeNameStateProvider.state).state, + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: "You send", + value: + "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: "You receive", + value: + "~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}", + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: model.rateType == ExchangeRateType.estimated + ? "Estimated rate" + : "Fixed rate", + value: model.rateInfo, + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + vertical: true, + label: "Recipient ${model.receiveTicker.toUpperCase()} address", + value: model.recipientAddress!, + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + vertical: true, + label: "Refund ${model.sendTicker.toUpperCase()} address", + value: model.refundAddress!, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Back", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Confirm", + buttonHeight: ButtonHeight.l, + onPressed: createTrade, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart new file mode 100644 index 000000000..5b69f064d --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -0,0 +1,301 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/route_generator.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/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/rounded_white_container.dart'; + +class DesktopStep4 extends ConsumerStatefulWidget { + const DesktopStep4({ + Key? key, + required this.model, + }) : super(key: key); + + final IncompleteExchangeModel model; + + @override + ConsumerState<DesktopStep4> createState() => _DesktopStep4State(); +} + +class _DesktopStep4State extends ConsumerState<DesktopStep4> { + late final IncompleteExchangeModel model; + + String _statusString = "New"; + + Timer? _statusTimer; + + bool _isWalletCoinAndHasWallet(String ticker) { + try { + final coin = coinFromTickerCaseInsensitive(ticker); + return ref + .read(walletsChangeNotifierProvider) + .managers + .where((element) => element.coin == coin) + .isNotEmpty; + } catch (_) { + return false; + } + } + + Future<void> _updateStatus() async { + final statusResponse = + await ref.read(exchangeProvider).updateTrade(model.trade!); + String status = "Waiting"; + if (statusResponse.value != null) { + status = statusResponse.value!.status; + } + + // extra info if status is waiting + if (status == "Waiting") { + status += " for deposit"; + } + + if (mounted) { + setState(() { + _statusString = status; + }); + } + } + + @override + void initState() { + model = widget.model; + + _statusTimer = Timer.periodic(const Duration(seconds: 60), (_) { + _updateStatus(); + }); + + super.initState(); + } + + @override + void dispose() { + _statusTimer?.cancel(); + _statusTimer = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + "Send ${model.sendTicker.toUpperCase()} to the address below", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + RoundedContainer( + color: Theme.of(context).extension<StackColors>()!.warningBackground, + child: RichText( + text: TextSpan( + text: + "You must send at least ${model.sendAmount.toString()} ${model.sendTicker}. ", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .warningForeground, + fontSize: 14, + ), + children: [ + TextSpan( + text: + "If you send less than ${model.sendAmount.toString()} ${model.sendTicker}, your transaction may not be converted and it may not be refunded.", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .warningForeground, + fontSize: 14, + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension<StackColors>()!.background, + padding: const EdgeInsets.all(0), + child: Column( + children: [ + DesktopStepItem( + vertical: true, + label: "Send ${model.sendTicker.toUpperCase()} to this address", + value: model.trade!.payInAddress, + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: "Amount", + value: + "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + DesktopStepItem( + label: "Trade ID", + value: model.trade!.tradeId, + ), + Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ), + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + Text( + _statusString, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .colorForStatus(_statusString), + ), + ), + ], + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Send from Stack Wallet", + buttonHeight: ButtonHeight.l, + onPressed: () { + final trade = model.trade!; + final amount = Decimal.parse(trade.payInAmount); + final address = trade.payInAddress; + + final coin = + coinFromTickerCaseInsensitive(trade.payInCurrency); + + showDialog<void>( + context: context, + builder: (context) => Navigator( + initialRoute: SendFromView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + SendFromView( + coin: coin, + trade: trade, + amount: amount, + address: address, + shouldPopRoot: true, + fromDesktopStep4: true, + ), + const RouteSettings( + name: SendFromView.routeName, + ), + ), + ]; + }, + ), + ); + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Show QR code", + buttonHeight: ButtonHeight.l, + onPressed: () { + showDialog<dynamic>( + context: context, + barrierColor: Colors.transparent, + barrierDismissible: true, + builder: (_) { + return DesktopDialog( + maxHeight: 720, + maxWidth: 720, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Send ${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker} to this address", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 48, + ), + Center( + child: QrImage( + // TODO: grab coin uri scheme from somewhere + // data: "${coin.uriScheme}:$receivingAddress", + data: model.trade!.payInAddress, + size: 290, + foregroundColor: Theme.of(context) + .extension<StackColors>()! + .accentColorDark, + ), + ), + const SizedBox( + height: 48, + ), + SecondaryButton( + label: "Cancel", + width: 310, + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ], + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart new file mode 100644 index 000000000..323517e13 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; + +class DesktopStepItem extends StatelessWidget { + const DesktopStepItem( + {Key? key, + required this.label, + required this.value, + this.padding = const EdgeInsets.all(16), + this.vertical = false}) + : super(key: key); + + final String label; + final String value; + final EdgeInsets padding; + final bool vertical; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: ConditionalParent( + condition: vertical, + builder: (child) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + child, + const SizedBox( + height: 2, + ), + Text( + value, + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension<StackColors>()!.textDark, + ), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + if (!vertical) + Text( + value, + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension<StackColors>()!.textDark, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart new file mode 100644 index 000000000..a3fb91f61 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart @@ -0,0 +1,329 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.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/format.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; + +class DesktopChooseFromStack extends ConsumerStatefulWidget { + const DesktopChooseFromStack({ + Key? key, + required this.coin, + }) : super(key: key); + + final Coin coin; + + @override + ConsumerState<DesktopChooseFromStack> createState() => + _DesktopChooseFromStackState(); +} + +class _DesktopChooseFromStackState + extends ConsumerState<DesktopChooseFromStack> { + late final TextEditingController _searchController; + late final FocusNode searchFieldFocusNode; + + String _searchTerm = ""; + + List<String> filter(List<String> walletIds, String searchTerm) { + if (searchTerm.isEmpty) { + return walletIds; + } + + final List<String> result = []; + for (final walletId in walletIds) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + if (manager.walletName.toLowerCase().contains(searchTerm.toLowerCase())) { + result.add(walletId); + } + } + + return result; + } + + @override + void initState() { + searchFieldFocusNode = FocusNode(); + _searchController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Choose from Stack", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 28, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Search", + searchFieldFocusNode, + context, + desktopMed: true, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 18, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + ), + ), + 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 SizedBox( + height: 16, + ), + Flexible( + child: Builder( + builder: (context) { + List<String> walletIds = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getWalletIdsFor(coin: widget.coin), + ), + ); + + if (walletIds.isEmpty) { + return Column( + children: [ + RoundedWhiteContainer( + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: Center( + child: Text( + "No ${widget.coin.ticker.toUpperCase()} wallets", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + ), + ], + ); + } + + walletIds = filter(walletIds, _searchTerm); + + return ListView.separated( + primary: false, + itemCount: walletIds.length, + separatorBuilder: (_, __) => const SizedBox( + height: 5, + ), + itemBuilder: (context, index) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletIds[index]))); + + return RoundedWhiteContainer( + borderColor: + Theme.of(context).extension<StackColors>()!.background, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 14, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + children: [ + WalletInfoCoinIcon(coin: widget.coin), + const SizedBox( + width: 12, + ), + Text( + manager.walletName, + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ), + ], + ), + const Spacer(), + BalanceDisplay( + walletId: walletIds[index], + ), + const SizedBox( + width: 80, + ), + BlueTextButton( + text: "Select wallet", + onTap: () { + Navigator.of(context).pop(manager.walletId); + }, + ), + ], + ), + ); + }, + ); + }, + ), + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + ], + ) + ], + ); + } +} + +class BalanceDisplay extends ConsumerStatefulWidget { + const BalanceDisplay({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState<BalanceDisplay> createState() => _BalanceDisplayState(); +} + +class _BalanceDisplayState extends ConsumerState<BalanceDisplay> { + late final String walletId; + + Decimal? _cachedBalance; + + static const loopedText = [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance..." + ]; + + @override + void initState() { + walletId = widget.walletId; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))); + final locale = ref.watch( + localeServiceChangeNotifierProvider.select((value) => value.locale)); + + return FutureBuilder( + future: manager.availableBalance, + builder: (context, AsyncSnapshot<Decimal> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + _cachedBalance = snapshot.data; + } + + if (_cachedBalance == null) { + return AnimatedText( + stringsToLoopThrough: loopedText, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension<StackColors>()!.textSubtitle1, + ), + ); + } else { + return Text( + "${Format.localizedStringAsFixed( + value: _cachedBalance!, + locale: locale, + decimalPlaces: 8, + )} ${manager.coin.ticker}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension<StackColors>()!.textSubtitle1, + ), + textAlign: TextAlign.right, + ); + } + }, + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart new file mode 100644 index 000000000..ddcd2e6c4 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class DesktopExchangeStepsIndicator extends StatelessWidget { + const DesktopExchangeStepsIndicator({Key? key, required this.currentStep}) + : super(key: key); + + final int currentStep; + + Color getColor(BuildContext context, int step) { + if (currentStep > step) { + return Theme.of(context) + .extension<StackColors>()! + .accentColorBlue + .withOpacity(0.5); + } else if (currentStep < step) { + return Theme.of(context).extension<StackColors>()!.textSubtitle3; + } else { + return Theme.of(context).extension<StackColors>()!.accentColorBlue; + } + } + + static const double verticalSpacing = 6; + static const double horizontalSpacing = 16; + static const double barHeight = 4; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Column( + children: [ + Text( + "Confirm amount", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 1), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 1), + height: barHeight, + width: double.infinity, + ), + ], + ), + ), + const SizedBox( + width: horizontalSpacing, + ), + Expanded( + child: Column( + children: [ + Text( + "Enter details", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 2), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 2), + height: barHeight, + width: double.infinity, + ), + ], + ), + ), + const SizedBox( + width: horizontalSpacing, + ), + Expanded( + child: Column( + children: [ + Text( + "Confirm details", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 3), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 3), + height: barHeight, + width: double.infinity, + ), + ], + ), + ), + const SizedBox( + width: horizontalSpacing, + ), + Expanded( + child: Column( + children: [ + Text( + "Complete exchange", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 4), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 4), + height: barHeight, + width: double.infinity, + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart new file mode 100644 index 000000000..e31a87dd4 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart @@ -0,0 +1,271 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; +import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; +import 'package:stackwallet/providers/global/trades_service_provider.dart'; +import 'package:stackwallet/providers/global/wallets_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/rounded_white_container.dart'; +import 'package:stackwallet/widgets/trade_card.dart'; + +import '../../../route_generator.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; + +class DesktopTradeHistory extends ConsumerStatefulWidget { + const DesktopTradeHistory({Key? key}) : super(key: key); + + @override + ConsumerState<DesktopTradeHistory> createState() => + _DesktopTradeHistoryState(); +} + +class _DesktopTradeHistoryState extends ConsumerState<DesktopTradeHistory> { + BorderRadius get _borderRadiusFirst { + return BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + BorderRadius get _borderRadiusLast { + return BorderRadius.only( + bottomLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + bottomRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + @override + Widget build(BuildContext context) { + final trades = + ref.watch(tradesServiceProvider.select((value) => value.trades)); + + final tradeCount = trades.length; + final hasHistory = tradeCount > 0; + + if (hasHistory) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Exchange details", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 16, + ), + Expanded( + child: ListView.separated( + shrinkWrap: true, + primary: false, + itemBuilder: (context, index) { + BorderRadius? radius; + if (index == tradeCount - 1) { + radius = _borderRadiusLast; + } else if (index == 0) { + radius = _borderRadiusFirst; + } + + return Container( + decoration: BoxDecoration( + color: Theme.of(context).extension<StackColors>()!.popupBG, + borderRadius: radius, + ), + child: TradeCard( + key: Key("tradeCard_${trades[index].uuid}"), + trade: trades[index], + onTap: () async { + final String tradeId = trades[index].tradeId; + + final lookup = + ref.read(tradeSentFromStackLookupProvider).all; + + debugPrint("ALL: $lookup"); + + final String? txid = ref + .read(tradeSentFromStackLookupProvider) + .getTxidForTradeId(tradeId); + final List<String>? walletIds = ref + .read(tradeSentFromStackLookupProvider) + .getWalletIdsForTradeId(tradeId); + + if (txid != null && + walletIds != null && + walletIds.isNotEmpty) { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletIds.first); + + debugPrint("name: ${manager.walletName}"); + + // TODO store tx data completely locally in isar so we don't lock up ui here when querying txData + final txData = await manager.transactionData; + + final tx = txData.getAllTransactions()[txid]; + + if (mounted) { + await showDialog<void>( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + // maxHeight: + // MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3( + context), + ), + DesktopDialogCloseButton( + onPressedOverride: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: tx, + walletName: manager.walletName, + walletId: walletIds.first, + ), + ), + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), + ), + ]; + }, + ), + ); + } + } else { + unawaited( + showDialog<void>( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + // maxHeight: + // MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3( + context), + ), + DesktopDialogCloseButton( + onPressedOverride: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: null, + walletName: null, + walletId: walletIds?.first, + ), + ), + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), + ), + ]; + }, + ), + ), + ); + } + }, + ), + ); + }, + separatorBuilder: (context, index) { + return Container( + height: 1, + color: Theme.of(context).extension<StackColors>()!.background, + ); + }, + itemCount: tradeCount, + ), + ), + ], + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "Trades will appear here", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + ); + } + } +} diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index f60ce2240..eb5dec18a 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -49,8 +49,15 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> { unawaited( showDialog( context: context, - builder: (context) => const LoadingIndicator( - width: 200, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], ), ), ); @@ -158,6 +165,12 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> { obscureText: hidePassword, enableSuggestions: false, autocorrect: false, + autofocus: true, + onSubmitted: (_) { + if (_continueEnabled) { + login(); + } + }, decoration: standardInputDecoration( "Enter password", passwordFocusNode, diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index ec40e5f60..e5432081c 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -2,21 +2,29 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; -import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/ui/address_book_providers/address_book_filter_provider.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/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/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.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'; @@ -34,13 +42,10 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> { late final FocusNode _searchFocusNode; - List<Contact>? _cache; - List<Contact>? _cacheFav; - - late bool hasContacts = false; - String _searchTerm = ""; + String? currentContactId; + Future<void> selectCryptocurrency() async { await showDialog<dynamic>( context: context, @@ -76,6 +81,47 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> { _searchController = TextEditingController(); _searchFocusNode = FocusNode(); + ref.refresh(addressBookFilterProvider); + + // if (widget.coin == null) { + List<Coin> coins = Coin.values.toList(); + coins.remove(Coin.firoTestNet); + + bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins; + + if (showTestNet) { + ref.read(addressBookFilterProvider).addAll(coins, false); + } else { + ref + .read(addressBookFilterProvider) + .addAll(coins.getRange(0, coins.length - kTestNetCoinCount), false); + } + // } else { + // ref.read(addressBookFilterProvider).add(widget.coin!, false); + // } + + WidgetsBinding.instance.addPostFrameCallback((_) async { + List<ContactAddressEntry> addresses = []; + final managers = ref.read(walletsChangeNotifierProvider).managers; + for (final manager in managers) { + addresses.add( + ContactAddressEntry( + coin: manager.coin, + address: await manager.currentReceivingAddress, + label: "Current Receiving", + other: manager.walletName, + ), + ); + } + final self = Contact( + name: "My Stack", + addresses: addresses, + isFavorite: true, + id: "default", + ); + await ref.read(addressBookServiceProvider).editContact(self); + }); + super.initState(); } @@ -90,7 +136,28 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final hasWallets = ref.watch(walletsChangeNotifierProvider).hasWallets; + final contacts = + ref.watch(addressBookServiceProvider.select((value) => value.contacts)); + + final allContacts = contacts + .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)) + .toList(); + + final favorites = 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(_searchTerm, e)) + .where((element) => element.isFavorite) + .toList(); return DesktopScaffold( appBar: DesktopAppBar( @@ -108,115 +175,262 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> { ), ), body: Padding( - padding: const EdgeInsets.all(24), - child: Row( - children: [ - Expanded( - flex: 6, - 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: (value) { - setState(() { - _searchTerm = value; - }); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 20, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, + padding: const EdgeInsets.only( + left: 24, + right: 24, + bottom: 24, + ), + child: DesktopAddressBookScaffold( + controlsLeft: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + 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: 20, + ), + 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, + ), + ), + ), + controlsRight: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SecondaryButton( + width: 184, + label: "Filter", + buttonHeight: ButtonHeight.l, + icon: SvgPicture.asset( + Assets.svg.filter, + color: Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondary, + ), + onPressed: selectCryptocurrency, + ), + const SizedBox( + width: 20, + ), + PrimaryButton( + width: 184, + label: "Add new", + buttonHeight: ButtonHeight.l, + icon: SvgPicture.asset( + Assets.svg.circlePlus, + color: Theme.of(context) + .extension<StackColors>()! + .buttonTextPrimary, + ), + onPressed: newContact, + ), + ], + ), + filterItems: Container(), + upperLabel: favorites.isEmpty && allContacts.isEmpty + ? null + : Text( + favorites.isEmpty ? "All contacts" : "Favorites", + style: STextStyles.smallMed12(context), + ), + lowerLabel: favorites.isEmpty + ? null + : Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 12, + ), + child: Text( + "All contacts", + style: STextStyles.smallMed12(context), + ), + ), + favorites: favorites.isEmpty + ? contacts.isNotEmpty + ? null + : RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ) + : RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + for (int i = 0; i < favorites.length; i++) + Column( + children: [ + if (i > 0) + Container( + color: Theme.of(context) + .extension<StackColors>()! + .background, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(4), + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .accentColorDark + .withOpacity( + currentContactId == favorites[i].id + ? 0.08 + : 0, + ), + child: RawMaterialButton( + onPressed: () { + setState(() { + currentContactId = favorites[i].id; + }); + }, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: AddressBookCard( + key: Key( + "favContactCard_${favorites[i].id}_key"), + contactId: favorites[i].id, + desktopSendFrom: false, ), ), - ) - : null, - ), - ), - ), - const SizedBox( - height: 24, - ), - const AddressBookView(), - ], - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - flex: 5, - child: Column( - children: [ - Row( - children: [ - SecondaryButton( - width: 184, - label: "Filter", - desktopMed: true, - icon: SvgPicture.asset( - Assets.svg.filter, - color: Theme.of(context) - .extension<StackColors>()! - .buttonTextSecondary, + ), + ), + ], ), - onPressed: selectCryptocurrency, - ), - const SizedBox( - width: 20, - ), - PrimaryButton( - width: 184, - label: "Add new", - desktopMed: true, - icon: SvgPicture.asset( - Assets.svg.circlePlus, - color: Theme.of(context) - .extension<StackColors>()! - .buttonTextPrimary, - ), - onPressed: newContact, - ), ], ), - ], - ), - ), - ], + ), + all: allContacts.isEmpty + ? contacts.isNotEmpty + ? null + : RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ) + : Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + for (int i = 0; i < allContacts.length; i++) + Column( + children: [ + if (i > 0) + Container( + color: Theme.of(context) + .extension<StackColors>()! + .background, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(4), + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .accentColorDark + .withOpacity( + currentContactId == allContacts[i].id + ? 0.08 + : 0, + ), + child: RawMaterialButton( + onPressed: () { + setState(() { + currentContactId = allContacts[i].id; + }); + }, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: AddressBookCard( + key: Key( + "favContactCard_${allContacts[i].id}_key"), + contactId: allContacts[i].id, + desktopSendFrom: false, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + details: currentContactId == null + ? Container() + : DesktopContactDetails( + contactId: currentContactId!, + ), ), ), ); diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart new file mode 100644 index 000000000..36e44e6d4 --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart @@ -0,0 +1,112 @@ +import 'package:flutter/widgets.dart'; + +class DesktopAddressBookScaffold extends StatelessWidget { + const DesktopAddressBookScaffold({ + Key? key, + required this.controlsLeft, + required this.controlsRight, + required this.filterItems, + required this.upperLabel, + required this.lowerLabel, + required this.favorites, + required this.all, + required this.details, + }) : super(key: key); + + final Widget? controlsLeft; + final Widget? controlsRight; + final Widget? filterItems; + final Widget? upperLabel; + final Widget? lowerLabel; + final Widget? favorites; + final Widget? all; + final Widget? details; + + static const double weirdRowHeight = 30; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + flex: 6, + child: controlsLeft ?? Container(), + ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 5, + child: controlsRight ?? Container(), + ), + ], + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + Expanded( + child: filterItems ?? Container(), + ), + ], + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 6, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + primary: false, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: weirdRowHeight, + child: upperLabel, + ), + favorites ?? Container(), + lowerLabel ?? Container(), + all ?? Container(), + ], + ), + ), + ), + ); + }, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 5, + child: Column( + children: [ + const SizedBox( + height: weirdRowHeight, + ), + Flexible( + child: details ?? Container(), + ), + ], + ), + ), + ], + ), + ) + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart new file mode 100644 index 000000000..4d58dc474 --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart'; +import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.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/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'; + +class DesktopAddressCard extends StatelessWidget { + const DesktopAddressCard({ + Key? key, + required this.entry, + required this.contactId, + this.clipboard = const ClipboardWrapper(), + }) : super(key: key); + + final ContactAddressEntry entry; + final String contactId; + final ClipboardInterface clipboard; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.iconFor( + coin: entry.coin, + ), + height: 32, + width: 32, + ), + const SizedBox( + width: 16, + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + "${entry.label} (${entry.coin.ticker})", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension<StackColors>()!.textDark, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + entry.address, + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 8, + ), + Row( + children: [ + BlueTextButton( + text: "Copy", + onTap: () { + clipboard.setData( + ClipboardData(text: entry.address), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + ), + if (contactId != "default") + const SizedBox( + width: 16, + ), + if (contactId != "default") + Consumer( + builder: (context, ref, child) { + return BlueTextButton( + text: "Edit", + onTap: () async { + ref.refresh( + addressEntryDataProviderFamilyRefresher); + ref.read(addressEntryDataProvider(0)).address = + entry.address; + ref.read(addressEntryDataProvider(0)).addressLabel = + entry.label; + ref.read(addressEntryDataProvider(0)).coin = + entry.coin; + + await showDialog<void>( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: 566, + child: Column( + children: [ + Row( + children: [ + const SizedBox( + width: 8, + ), + const AppBarBackButton( + isCompact: true, + ), + Text( + "Edit address", + style: STextStyles.desktopH3(context), + ), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 20, + left: 32, + right: 32, + bottom: 32, + ), + child: EditContactAddressView( + contactId: contactId, + addressEntry: entry, + ), + ), + ), + ], + ), + ), + ); + }, + ); + }, + ), + ], + ) + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart new file mode 100644 index 000000000..62cd993af --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -0,0 +1,352 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; +import 'package:stackwallet/services/coins/manager.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/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/transaction_card.dart'; +import 'package:tuple/tuple.dart'; + +class DesktopContactDetails extends ConsumerStatefulWidget { + const DesktopContactDetails({ + Key? key, + required this.contactId, + }) : super(key: key); + + final String contactId; + + @override + ConsumerState<DesktopContactDetails> createState() => + _DesktopContactDetailsState(); +} + +class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> { + List<Tuple2<String, Transaction>> _cachedTransactions = []; + + bool _contactHasAddress(String address, Contact contact) { + for (final entry in contact.addresses) { + if (entry.address == address) { + return true; + } + } + return false; + } + + Future<List<Tuple2<String, Transaction>>> _filteredTransactionsByContact( + List<Manager> managers, + ) async { + final contact = + ref.read(addressBookServiceProvider).getContactById(widget.contactId); + + // TODO: optimise + + List<Tuple2<String, Transaction>> result = []; + for (final manager in managers) { + final transactions = (await manager.transactionData) + .getAllTransactions() + .values + .toList() + .where((e) => _contactHasAddress(e.address, contact)); + + for (final tx in transactions) { + result.add(Tuple2(manager.walletId, tx)); + } + } + // sort by date + result.sort((a, b) => b.item2.timestamp - a.item2.timestamp); + + return result; + } + + @override + Widget build(BuildContext context) { + // provider hack to prevent trying to update widget with deleted contact + Contact? _contact; + try { + _contact = ref.watch(addressBookServiceProvider + .select((value) => value.getContactById(widget.contactId))); + } catch (_) { + return Container(); + } + + final contact = _contact!; + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: contact.id == "default" + ? Colors.transparent + : Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(32), + ), + child: contact.id == "default" + ? Center( + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 32, + ), + ) + : contact.emojiChar != null + ? Center( + child: Text(contact.emojiChar!), + ) + : Center( + child: SvgPicture.asset( + Assets.svg.user, + width: 18, + ), + ), + ), + const SizedBox( + width: 16, + ), + Text( + contact.name, + style: STextStyles.desktopTextSmall(context), + ), + ], + ), + if (widget.contactId != "default") + SecondaryButton( + label: "Options", + width: 96, + buttonHeight: ButtonHeight.xxs, + onPressed: () async { + await showDialog<void>( + context: context, + barrierColor: Colors.transparent, + builder: (context) { + return DesktopContactOptionsMenuPopup( + contactId: contact.id, + ); + }, + ); + }, + ), + ], + ), + const SizedBox( + height: 24, + ), + Flexible( + child: ListView( + primary: false, + shrinkWrap: true, + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Addresses", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + BlueTextButton( + text: "Add new", + onTap: () async { + ref.refresh( + addressEntryDataProviderFamilyRefresher); + + await showDialog<void>( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: 566, + child: Column( + children: [ + Row( + children: [ + const SizedBox( + width: 8, + ), + const AppBarBackButton( + isCompact: true, + ), + Text( + "Add new address", + style: + STextStyles.desktopH3(context), + ), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 20, + left: 32, + right: 32, + bottom: 32, + ), + child: AddNewContactAddressView( + contactId: widget.contactId, + ), + ), + ), + ], + ), + ), + ); + }, + ), + ], + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < contact.addresses.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (i > 0) + Container( + color: Theme.of(context) + .extension<StackColors>()! + .background, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(18), + child: DesktopAddressCard( + entry: contact.addresses[i], + contactId: contact.id, + ), + ), + ], + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 12, + ), + child: Text( + "Transaction history", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + 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), + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: Column( + mainAxisSize: MainAxisSize.min, + 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), + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ..._cachedTransactions.map( + (e) => TransactionCard( + key: Key( + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), + transaction: e.item2, + walletId: e.item1, + ), + ), + ], + ), + ); + } + } + }, + ), + ], + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart new file mode 100644 index 000000000..690d1be98 --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart @@ -0,0 +1,402 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.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/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'; + +class DesktopContactOptionsMenuPopup extends ConsumerStatefulWidget { + const DesktopContactOptionsMenuPopup({Key? key, required this.contactId}) + : super(key: key); + + final String contactId; + + @override + ConsumerState<DesktopContactOptionsMenuPopup> createState() => + _DesktopContactOptionsMenuPopupState(); +} + +class _DesktopContactOptionsMenuPopupState + extends ConsumerState<DesktopContactOptionsMenuPopup> { + bool hoveredOnStar = false; + bool hoveredOnPencil = false; + bool hoveredOnTrash = false; + + void editContact() { + // pop context menu + Navigator.of(context).pop(); + + showDialog<dynamic>( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => DesktopDialog( + maxWidth: 580, + maxHeight: 400, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Edit contact", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + right: 32, + bottom: 32, + ), + child: EditContactNameEmojiView( + contactId: widget.contactId, + ), + ), + ), + ], + ), + ), + ); + } + + void attemptDeleteContact() { + final contact = + ref.read(addressBookServiceProvider).getContactById(widget.contactId); + + // pop context menu + Navigator.of(context).pop(); + + showDialog<dynamic>( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => DesktopDialog( + maxWidth: 500, + maxHeight: 400, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Delete ${contact.name}?", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Spacer( + flex: 1, + ), + Text( + "Contact will be deleted permanently!", + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + buttonHeight: ButtonHeight.l, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Consumer( + builder: (context, ref, __) => PrimaryButton( + label: "Delete", + buttonHeight: ButtonHeight.l, + onPressed: () { + ref + .read(addressBookServiceProvider) + .removeContact(contact.id); + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.success, + message: "${contact.name} deleted", + context: context, + ); + }, + ), + ), + ), + ], + ) + ], + ), + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + top: 210, + left: MediaQuery.of(context).size.width - 280, + child: Container( + width: 270, + decoration: BoxDecoration( + color: Theme.of(context).extension<StackColors>()!.popupBG, + borderRadius: BorderRadius.circular( + 20, + ), + boxShadow: [ + Theme.of(context).extension<StackColors>()!.standardBoxShadow, + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnStar = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnStar = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () { + final contact = + ref.read(addressBookServiceProvider).getContactById( + widget.contactId, + ); + ref.read(addressBookServiceProvider).editContact( + contact.copyWith( + isFavorite: !contact.isFavorite, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.star, + width: 24, + height: 22, + color: hoveredOnStar + ? Theme.of(context) + .extension<StackColors>()! + .textDark + : Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + ref.watch(addressBookServiceProvider.select( + (value) => value + .getContactById(widget.contactId) + .isFavorite)) + ? "Remove from favorites" + : "Add to favorites", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + if (widget.contactId != "default") + const SizedBox( + height: 2, + ), + if (widget.contactId != "default") + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnPencil = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnPencil = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: editContact, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 24, + height: 22, + color: hoveredOnPencil + ? Theme.of(context) + .extension<StackColors>()! + .textDark + : Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + "Edit contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + if (widget.contactId != "default") + const SizedBox( + height: 2, + ), + if (widget.contactId != "default") + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnTrash = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnTrash = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: attemptDeleteContact, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.trash, + width: 24, + height: 22, + color: hoveredOnTrash + ? Theme.of(context) + .extension<StackColors>()! + .textDark + : Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + "Delete contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index b1c35f00b..9791cd867 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; @@ -29,10 +30,11 @@ class _DesktopHomeViewState extends ConsumerState<DesktopHomeView> { onGenerateRoute: RouteGenerator.generateRoute, initialRoute: MyStackView.routeName, ), - // Container( - // // todo: exchange - // color: Colors.green, - // ), + DesktopMenuItemId.exchange: const Navigator( + key: Key("desktopExchangeHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopExchangeView.routeName, + ), DesktopMenuItemId.notifications: const Navigator( key: Key("desktopNotificationsHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index bdaa1d6ce..d82d62883 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu_item.dart'; import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +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'; @@ -104,10 +105,10 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .state ? Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark : Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "My Stack", @@ -120,46 +121,57 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { const SizedBox( height: 2, ), - // DesktopMenuItem( - // icon: SvgPicture.asset( - // Assets.svg.exchangeDesktop, - // width: 20, - // height: 20, - // color: DesktopMenuItemId.exchange == ref.watch(currentDesktopMenuItemProvider.state).state - // ? Theme.of(context) - // .extension<StackColors>()! - // .textDark - // : Theme.of(context) - // .extension<StackColors>()! - // .textDark - // .withOpacity(0.8), - // ), - // label: "Exchange", - // value: DesktopMenuItemId.exchange, - // group: ref.watch(currentDesktopMenuItemProvider.state).state, - // onChanged: updateSelectedMenuItem, - // iconOnly: _width == minimizedWidth, - // ), - // const SizedBox( - // height: 2, - // ), DesktopMenuItem( icon: SvgPicture.asset( - Assets.svg.bell, + Assets.svg.exchangeDesktop, width: 20, height: 20, - color: DesktopMenuItemId.notifications == + color: DesktopMenuItemId.exchange == ref .watch(currentDesktopMenuItemProvider.state) .state ? Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark : Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark .withOpacity(0.8), ), + label: "Exchange", + value: DesktopMenuItemId.exchange, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, + onChanged: updateSelectedMenuItem, + iconOnly: _width == minimizedWidth, + ), + const SizedBox( + height: 2, + ), + DesktopMenuItem( + 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 + : DesktopMenuItemId.notifications == + ref + .watch(currentDesktopMenuItemProvider + .state) + .state + ? Theme.of(context) + .extension<StackColors>()! + .accentColorDark + : Theme.of(context) + .extension<StackColors>()! + .accentColorDark + .withOpacity(0.8), + ), label: "Notifications", value: DesktopMenuItemId.notifications, group: @@ -181,10 +193,10 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .state ? Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark : Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Address Book", @@ -208,10 +220,10 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .state ? Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark : Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Settings", @@ -235,10 +247,10 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .state ? Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark : Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Support", @@ -262,10 +274,10 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .state ? Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark : Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "About", @@ -283,7 +295,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { height: 20, color: Theme.of(context) .extension<StackColors>()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Exit", diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 81b531632..5996597b5 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -7,6 +8,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/my_wallet.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart'; @@ -15,6 +17,7 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_vie import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; @@ -27,8 +30,11 @@ 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/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/hover_text_field.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -165,6 +171,116 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> { } } + Future<void> attemptAnonymize() async { + final managerProvider = + ref.read(walletsChangeNotifierProvider).getManagerProvider(walletId); + + bool shouldPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Anonymizing balance", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), + ), + ); + final firoWallet = ref.read(managerProvider).wallet as FiroWallet; + + final publicBalance = await firoWallet.availablePublicBalance(); + if (publicBalance <= Decimal.zero) { + shouldPop = true; + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "No funds available to anonymize!", + context: context, + ), + ); + } + return; + } + + try { + await firoWallet.anonymizeAllPublicFunds(); + shouldPop = true; + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Anonymize transaction submitted", + context: context, + ), + ); + } + } catch (e) { + shouldPop = true; + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + await showDialog<dynamic>( + context: context, + builder: (_) => DesktopDialog( + maxWidth: 400, + maxHeight: 300, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Anonymize all failed", + style: STextStyles.desktopH3(context), + ), + const Spacer( + flex: 1, + ), + Text( + "Reason: $e", + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: + Navigator.of(context, rootNavigator: true).pop, + ), + ), + ], + ) + ], + ), + ), + ), + ); + } + } + } + @override void initState() { controller = TextEditingController(); @@ -297,6 +413,12 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> { WalletKeysButton( walletId: walletId, ), + const SizedBox( + width: 2, + ), + DeleteWalletButton( + walletId: walletId, + ), const SizedBox( width: 12, ), @@ -333,35 +455,96 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> { : WalletSyncStatus.synced, ), const Spacer(), - SecondaryButton( - width: 180, - desktopMed: true, - onPressed: () { - _onExchangePressed(context); - }, - label: "Exchange", - icon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) - .extension<StackColors>()! - .buttonBackPrimary - .withOpacity(0.2), - ), - child: Center( - child: SvgPicture.asset( - Assets.svg.arrowRotate2, - width: 14, - height: 14, - color: Theme.of(context) - .extension<StackColors>()! - .buttonTextSecondary, - ), - ), + if (coin == Coin.firo) const SizedBox(width: 10), + if (coin == Coin.firo) + SecondaryButton( + width: 180, + buttonHeight: ButtonHeight.l, + label: "Anonymize funds", + onPressed: () async { + await showDialog<void>( + context: context, + barrierDismissible: false, + builder: (context) => DesktopDialog( + maxWidth: 500, + maxHeight: 210, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, vertical: 20), + child: Column( + children: [ + Text( + "Attention!", + style: STextStyles.desktopH2(context), + ), + const SizedBox(height: 16), + Text( + "You're about to anonymize all of your public funds.", + style: + STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of(context).pop(); + + unawaited(attemptAnonymize()); + }, + ) + ], + ), + ], + ), + ), + ), + ); + }, ), - ), + // if (coin == Coin.firo) const SizedBox(width: 16), + // SecondaryButton( + // width: 180, + // buttonHeight: ButtonHeight.l, + // onPressed: () { + // _onExchangePressed(context); + // }, + // label: "Exchange", + // icon: Container( + // width: 24, + // height: 24, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(24), + // color: Theme.of(context) + // .extension<StackColors>()! + // .buttonBackPrimary + // .withOpacity(0.2), + // ), + // child: Center( + // child: SvgPicture.asset( + // Assets.svg.arrowRotate2, + // width: 14, + // height: 14, + // color: Theme.of(context) + // .extension<StackColors>()! + // .buttonTextSecondary, + // ), + // ), + // ), + // ), ], ), ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart index 9f309a08e..92dd9f6fc 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart @@ -200,12 +200,19 @@ class _AddressBookAddressChooserState extends State<AddressBookAddressChooser> { final favorites = pullOutFavorites(contacts); - return ListView.builder( + final totalLength = favorites.length + + contacts.length + + 2; // +2 for "fav" and "all" headers + + return ListView.separated( primary: false, shrinkWrap: true, - itemCount: favorites.length + - contacts.length + - 2, // +2 for "fav" and "all" headers + itemCount: totalLength, + separatorBuilder: (context, index) { + return const SizedBox( + height: 10, + ); + }, itemBuilder: (context, index) { if (index == 0) { return Padding( @@ -220,7 +227,7 @@ class _AddressBookAddressChooserState extends State<AddressBookAddressChooser> { STextStyles.desktopTextExtraExtraSmall(context), ), ); - } else if (index <= favorites.length) { + } else if (index < favorites.length + 1) { final id = favorites[index - 1].id; return ContactListItem( key: Key("contactContactListItem_${id}_key"), @@ -241,7 +248,7 @@ class _AddressBookAddressChooserState extends State<AddressBookAddressChooser> { ), ); } else { - final id = contacts[index - favorites.length - 1].id; + final id = contacts[index - favorites.length - 2].id; return ContactListItem( key: Key("contactContactListItem_${id}_key"), contactId: id, diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart index e030f9882..d7bfefb1f 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart @@ -58,7 +58,7 @@ class _ContactListItemState extends ConsumerState<ContactListItem> { ), child: AddressBookCard( contactId: contactId, - indicatorDown: _state == ExpandableState.expanded, + indicatorDown: _state, ), ), body: Column( @@ -87,35 +87,47 @@ class _ContactListItemState extends ConsumerState<ContactListItem> { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - WalletInfoCoinIcon(coin: e.coin), - const SizedBox( - width: 12, - ), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${contactId == "default" ? e.other! : e.label} (${e.coin.ticker})", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .textDark, - ), + Flexible( + child: Row( + children: [ + WalletInfoCoinIcon(coin: e.coin), + const SizedBox( + width: 12, + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "${contactId == "default" ? e.other! : e.label} (${e.coin.ticker})", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ), + Row( + children: [ + Flexible( + child: Text( + e.address, + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + ), + ], + ), + ], ), - Text( - e.address, - style: STextStyles - .desktopTextExtraExtraSmall(context), - ), - ], - ), - ], + ), + ], + ), ), BlueTextButton( text: "Select wallet", diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart new file mode 100644 index 000000000..a2071e7d6 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart @@ -0,0 +1,164 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; +import 'package:stackwallet/route_generator.dart'; +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'; + +class DeleteWalletButton extends ConsumerStatefulWidget { + const DeleteWalletButton({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState<DeleteWalletButton> createState() => _DeleteWalletButton(); +} + +class _DeleteWalletButton extends ConsumerState<DeleteWalletButton> { + late final String walletId; + + @override + void initState() { + walletId = widget.walletId; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000), + ), + onPressed: () async { + final shouldOpenDeleteDialog = await showDialog<bool?>( + context: context, + barrierColor: Colors.transparent, + builder: (context) { + return DeletePopupButton( + onTap: () async { + Navigator.of(context).pop(true); + }, + ); + }, + ); + + if (shouldOpenDeleteDialog == true) { + final result = await showDialog<bool?>( + context: context, + barrierDismissible: false, + builder: (context) => Navigator( + initialRoute: DesktopDeleteWalletDialog.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + RouteGenerator.generateRoute( + RouteSettings( + name: DesktopDeleteWalletDialog.routeName, + arguments: walletId, + ), + ), + ]; + }, + ), + ); + + if (result == true) { + if (mounted) { + Navigator.of(context).pop(); + } + } + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 19, + horizontal: 32, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.ellipsis, + width: 20, + height: 20, + color: Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondary, + ), + ], + ), + ), + ); + } +} + +class DeletePopupButton extends StatefulWidget { + const DeletePopupButton({ + Key? key, + this.onTap, + }) : super(key: key); + + final VoidCallback? onTap; + + @override + State<DeletePopupButton> createState() => _DeletePopupButtonState(); +} + +class _DeletePopupButtonState extends State<DeletePopupButton> { + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + top: 24, + left: MediaQuery.of(context).size.width - 234, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.onTap, + child: Container( + width: 210, + height: 70, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * 2, + ), + color: Theme.of(context).extension<StackColors>()!.popupBG, + boxShadow: [ + Theme.of(context) + .extension<StackColors>()! + .standardBoxShadow, + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(width: 24), + SvgPicture.asset( + Assets.svg.trash, + ), + const SizedBox(width: 14), + Text( + "Delete wallet", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark), + ), + ], + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart new file mode 100644 index 000000000..70f4a3e13 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -0,0 +1,256 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/global/wallets_service_provider.dart'; +import 'package:stackwallet/route_generator.dart'; +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/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'; + +class DeleteWalletKeysPopup extends ConsumerStatefulWidget { + const DeleteWalletKeysPopup({ + Key? key, + required this.walletId, + required this.words, + this.clipboardInterface = const ClipboardWrapper(), + }) : super(key: key); + + final String walletId; + final List<String> words; + final ClipboardInterface clipboardInterface; + + static const String routeName = "/desktopDeleteWalletKeysPopup"; + + @override + ConsumerState<DeleteWalletKeysPopup> createState() => + _DeleteWalletKeysPopup(); +} + +class _DeleteWalletKeysPopup extends ConsumerState<DeleteWalletKeysPopup> { + late final String _walletId; + late final List<String> _words; + late final ClipboardInterface _clipboardInterface; + + @override + void initState() { + _walletId = widget.walletId; + _words = widget.words; + _clipboardInterface = widget.clipboardInterface; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 614, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Wallet keys", + style: STextStyles.desktopH3(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + }, + ), + ], + ), + const SizedBox( + height: 28, + ), + Text( + "Recovery phrase", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 8, + ), + Center( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: Text( + "Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.", + style: STextStyles.desktopTextExtraExtraSmall(context), + textAlign: TextAlign.center, + ), + ), + ), + const SizedBox( + height: 24, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: RawMaterialButton( + hoverColor: Colors.transparent, + onPressed: () async { + await _clipboardInterface.setData( + ClipboardData(text: _words.join(" ")), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + child: MnemonicTable( + words: widget.words, + isDesktop: true, + itemBorderColor: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + ), + ), + ), + const SizedBox( + height: 24, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: Row( + children: [ + Expanded( + child: PrimaryButton( + label: "Continue", + onPressed: () async { + await Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (context) { + return ConfirmDelete( + walletId: _walletId, + ); + }, + settings: const RouteSettings( + name: "/desktopConfirmDelete", + ), + ), + ); + }, + ), + ), + ], + ), + ), + const SizedBox( + height: 32, + ), + ], + ), + ); + } +} + +class ConfirmDelete extends ConsumerStatefulWidget { + const ConfirmDelete({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState<ConfirmDelete> createState() => _ConfirmDeleteState(); +} + +class _ConfirmDeleteState extends ConsumerState<ConfirmDelete> { + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxHeight: 350, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Thanks! " + "\n\nYour wallet will be deleted.", + style: STextStyles.desktopH2(context), + textAlign: TextAlign.center, + ), + const SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Continue", + onPressed: () async { + final walletsInstance = + ref.read(walletsChangeNotifierProvider); + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); + + final _managerWalletId = manager.walletId; + // + await ref + .read(walletsServiceChangeNotifierProvider) + .deleteWallet(manager.walletName, true); + + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(true); + } + + // wait for widget tree to dispose of any widgets watching the manager + await Future<void>.delayed(const Duration(seconds: 1)); + walletsInstance.removeWallet(walletId: _managerWalletId); + }, + ), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart new file mode 100644 index 000000000..a614be3a6 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.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:tuple/tuple.dart'; + +class DesktopAttentionDeleteWallet extends ConsumerStatefulWidget { + const DesktopAttentionDeleteWallet({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + static const String routeName = "/desktopAttentionDeleteWallet"; + + @override + ConsumerState<DesktopAttentionDeleteWallet> createState() => + _DesktopAttentionDeleteWallet(); +} + +class _DesktopAttentionDeleteWallet + extends ConsumerState<DesktopAttentionDeleteWallet> { + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 610, + maxHeight: 530, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + }, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Column( + children: [ + Text( + "Attention!", + style: STextStyles.desktopH2(context), + ), + const SizedBox( + height: 16, + ), + RoundedContainer( + color: Theme.of(context) + .extension<StackColors>()! + .snackBarBackError, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + "You are going to permanently delete you wallet.\n\nIf you delete your wallet, " + "the only way you can have access to your funds is by using your backup key." + "\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet." + "\n\nPLEASE SAVE YOUR BACKUP KEY.", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark3, + ), + ), + ), + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + }, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "View Backup Key", + onPressed: () async { + final words = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .mnemonic; + + if (mounted) { + await Navigator.of(context).pushNamed( + DeleteWalletKeysPopup.routeName, + arguments: Tuple2( + widget.walletId, + words, + ), + ); + } + }, + ), + ], + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart index 566a82b35..a8d1ea497 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -10,6 +12,9 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; +import '../../../../../notifications/show_flush_bar.dart'; +import '../../../../../widgets/loading_indicator.dart'; + class DesktopAuthSend extends ConsumerStatefulWidget { const DesktopAuthSend({Key? key}) : super(key: key); @@ -142,7 +147,7 @@ class _DesktopAuthSendState extends ConsumerState<DesktopAuthSend> { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -153,14 +158,37 @@ class _DesktopAuthSendState extends ConsumerState<DesktopAuthSend> { child: PrimaryButton( enabled: _confirmEnabled, label: "Confirm", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { - // TODO show spinner while verifying passphrase + unawaited( + showDialog<void>( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future<void>.delayed(const Duration(seconds: 1)); final passwordIsValid = await verifyPassphrase(); if (mounted) { - Navigator.of(context).pop(passwordIsValid); + Navigator.of(context).pop(); + Navigator.of( + context, + rootNavigator: true, + ).pop(passwordIsValid); + await Future<void>.delayed(const Duration( + milliseconds: 100, + )); } }, ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart new file mode 100644 index 000000000..9c890b223 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; + +class DesktopBalanceToggleButton extends ConsumerWidget { + const DesktopBalanceToggleButton({ + Key? key, + this.onPressed, + }) : super(key: key); + + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SizedBox( + height: 22, + width: 22, + child: MaterialButton( + color: Theme.of(context).extension<StackColors>()!.buttonBackSecondary, + splashColor: Theme.of(context).extension<StackColors>()!.highlight, + onPressed: () { + if (ref.read(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available) { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.full; + } else { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.available; + } + onPressed?.call(); + }, + elevation: 0, + highlightElevation: 0, + hoverElevation: 0, + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Center( + child: Image( + image: AssetImage( + ref.watch(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available + ? Assets.png.glassesHidden + : Assets.png.glasses, + ), + width: 16, + ), + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart new file mode 100644 index 000000000..e8d9c2dc5 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart @@ -0,0 +1,256 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +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_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +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/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/loading_indicator.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; + +class DesktopDeleteWalletDialog extends ConsumerStatefulWidget { + const DesktopDeleteWalletDialog({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + static const String routeName = "/desktopDeleteWalletDialog"; + + @override + ConsumerState<DesktopDeleteWalletDialog> createState() => + _DesktopDeleteWalletDialog(); +} + +class _DesktopDeleteWalletDialog + extends ConsumerState<DesktopDeleteWalletDialog> { + late final TextEditingController passwordController; + late final FocusNode passwordFocusNode; + + bool hidePassword = true; + bool _continueEnabled = false; + + Future<void> enterPassphrase() async { + unawaited( + showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future<void>.delayed(const Duration(seconds: 1)); + + final verified = await ref + .read(storageCryptoHandlerProvider) + .verifyPassphrase(passwordController.text); + + if (verified) { + Navigator.of(context, rootNavigator: true).pop(); + + final words = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .mnemonic; + + if (mounted) { + Navigator.of(context).pop(); + + unawaited( + Navigator.of(context).pushNamed( + DesktopAttentionDeleteWallet.routeName, + arguments: widget.walletId, + ), + ); + } + } else { + Navigator.of(context, rootNavigator: true).pop(); + + await Future<void>.delayed(const Duration(milliseconds: 300)); + + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid passphrase!", + context: context, + ), + ); + } + } + + @override + void initState() { + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordController.dispose(); + passwordFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Column( + children: [ + const SizedBox(height: 16), + Text( + "Delete wallet", + style: STextStyles.desktopH2(context), + ), + const SizedBox(height: 16), + Text( + "Enter your password", + style: STextStyles.desktopTextMedium(context).copyWith( + color: + Theme.of(context).extension<StackColors>()!.textDark3, + ), + ), + const SizedBox(height: 24), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("desktopDeleteWalletPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + autofocus: true, + onSubmitted: (_) { + if (_continueEnabled) { + enterPassphrase(); + } + }, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + labelStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox( + width: 24, + ), + GestureDetector( + key: const Key( + "desktopDeleteWalletShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension<StackColors>()! + .textDark3, + width: 24, + height: 24, + ), + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + onChanged: (newValue) { + setState(() { + _continueEnabled = passwordController.text.isNotEmpty; + }); + }, + ), + ), + const SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + enabled: _continueEnabled, + label: "Continue", + onPressed: _continueEnabled + ? () async { + // add loading indicator + enterPassphrase(); + } + : null, + ), + ], + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 9a59c3ec1..1dd2e607e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -117,78 +117,82 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> { return 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: Container( - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).extension<StackColors>()!.background, - width: 2, + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + clipboard.setData( + ClipboardData(text: receivingAddress), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).extension<StackColors>()!.background, + width: 2, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - 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: 15, - height: 15, - color: Theme.of(context) - .extension<StackColors>()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ], - ), - const SizedBox( - height: 8, - ), - Row( - children: [ - Expanded( - child: Text( - receivingAddress, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .textDark, + 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: 15, + height: 15, + color: Theme.of(context) + .extension<StackColors>()! + .infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ], + ), + const SizedBox( + height: 8, + ), + Row( + children: [ + Expanded( + child: Text( + receivingAddress, + style: + STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), ), ), @@ -199,7 +203,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> { ), if (coin != Coin.epicCash) SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: generateNewAddress, label: "Generate new address", ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index af2c2517a..336dd7b4e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -145,7 +145,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> { right: 32, ), child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Ok", onPressed: () { Navigator.of(context).pop(); @@ -232,7 +232,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { Navigator.of(context).pop(false); @@ -244,7 +244,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Yes", onPressed: () { Navigator.of(context).pop(true); @@ -399,7 +399,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> { ), child: Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Yes", onPressed: () { Navigator.of( @@ -1385,7 +1385,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> { height: 36, ), PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Preview send", enabled: ref.watch(previewTxButtonStateProvider.state).state, onPressed: ref.watch(previewTxButtonStateProvider.state).state diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index 7a9e93467..d6e99ce70 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -1,13 +1,15 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -33,19 +35,6 @@ class _WDesktopWalletSummaryState extends State<DesktopWalletSummary> { late final String walletId; late final ChangeNotifierProvider<Manager> managerProvider; - void showSheet() { - showModalBottomSheet<dynamic>( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => WalletBalanceToggleSheet(walletId: walletId), - ); - } - Decimal? _balanceTotalCached; Decimal? _balanceCached; @@ -59,225 +48,161 @@ class _WDesktopWalletSummaryState extends State<DesktopWalletSummary> { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( + return Consumer( + builder: (context, ref, __) { + final Coin coin = + ref.watch(managerProvider.select((value) => value.coin)); + return Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Consumer( - builder: (_, ref, __) { - final Coin coin = - ref.watch(managerProvider.select((value) => value.coin)); - final externalCalls = ref.watch(prefsChangeNotifierProvider - .select((value) => value.externalCalls)); + Column( + children: [ + Consumer( + builder: (_, ref, __) { + final externalCalls = ref.watch(prefsChangeNotifierProvider + .select((value) => value.externalCalls)); - Future<Decimal>? totalBalanceFuture; - Future<Decimal>? availableBalanceFuture; - if (coin == Coin.firo || coin == Coin.firoTestNet) { - final firoWallet = - ref.watch(managerProvider.select((value) => value.wallet)) + Future<Decimal>? totalBalanceFuture; + Future<Decimal>? availableBalanceFuture; + if (coin == Coin.firo || coin == Coin.firoTestNet) { + final firoWallet = ref.watch( + managerProvider.select((value) => value.wallet)) as FiroWallet; - totalBalanceFuture = firoWallet.availablePublicBalance(); - availableBalanceFuture = firoWallet.availablePrivateBalance(); - } else { - totalBalanceFuture = ref.watch( - managerProvider.select((value) => value.totalBalance)); - - availableBalanceFuture = ref.watch(managerProvider - .select((value) => value.availableBalance)); - } - - final locale = ref.watch(localeServiceChangeNotifierProvider - .select((value) => value.locale)); - - final baseCurrency = ref.watch(prefsChangeNotifierProvider - .select((value) => value.currency)); - - final priceTuple = ref.watch(priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin))); - - final _showAvailable = false; - // ref.watch(walletBalanceToggleStateProvider.state).state == - // WalletBalanceToggleState.available; - - return FutureBuilder( - future: _showAvailable - ? availableBalanceFuture - : totalBalanceFuture, - builder: (fbContext, AsyncSnapshot<Decimal> snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - if (_showAvailable) { - _balanceCached = snapshot.data!; - } else { - _balanceTotalCached = snapshot.data!; - } - } - Decimal? balanceToShow = - _showAvailable ? _balanceCached : _balanceTotalCached; - - if (balanceToShow != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // GestureDetector( - // onTap: showSheet, - // child: Row( - // children: [ - // if (coin == Coin.firo || - // coin == Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Private" : "Public"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension<StackColors>()! - // .textFavoriteCard, - // ), - // ), - // if (coin != Coin.firo && - // coin != Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Available" : "Full"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension<StackColors>()! - // .textFavoriteCard, - // ), - // ), - // const SizedBox( - // width: 4, - // ), - // SvgPicture.asset( - // Assets.svg.chevronDown, - // color: Theme.of(context) - // .extension<StackColors>()! - // .textFavoriteCard, - // width: 8, - // height: 4, - // ), - // ], - // ), - // ), - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "${Format.localizedStringAsFixed( - value: balanceToShow, - locale: locale, - decimalPlaces: 8, - )} ${coin.ticker}", - style: STextStyles.desktopH3(context), - ), - ), - if (externalCalls) - Text( - "${Format.localizedStringAsFixed( - value: priceTuple.item1 * balanceToShow, - locale: locale, - decimalPlaces: 2, - )} $baseCurrency", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .textSubtitle1, - ), - ), - ], - ); + totalBalanceFuture = firoWallet.availablePublicBalance(); + availableBalanceFuture = + firoWallet.availablePrivateBalance(); } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // GestureDetector( - // onTap: showSheet, - // child: Row( - // children: [ - // if (coin == Coin.firo || - // coin == Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Private" : "Public"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension<StackColors>()! - // .textFavoriteCard, - // ), - // ), - // if (coin != Coin.firo && - // coin != Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Available" : "Full"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension<StackColors>()! - // .textFavoriteCard, - // ), - // ), - // const SizedBox( - // width: 4, - // ), - // SvgPicture.asset( - // Assets.svg.chevronDown, - // width: 8, - // height: 4, - // color: Theme.of(context) - // .extension<StackColors>()! - // .textFavoriteCard, - // ), - // ], - // ), - // ), - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance..." - ], - style: STextStyles.desktopH3(context).copyWith( - fontSize: 24, - color: Theme.of(context) - .extension<StackColors>()! - .textFavoriteCard, - ), - ), - if (externalCalls) - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance..." - ], - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .textSubtitle1, - ), - ), - ], - ); + totalBalanceFuture = ref.watch(managerProvider + .select((value) => value.totalBalance)); + + availableBalanceFuture = ref.watch(managerProvider + .select((value) => value.availableBalance)); } + + final locale = ref.watch(localeServiceChangeNotifierProvider + .select((value) => value.locale)); + + final baseCurrency = ref.watch(prefsChangeNotifierProvider + .select((value) => value.currency)); + + final priceTuple = ref.watch( + priceAnd24hChangeNotifierProvider + .select((value) => value.getPrice(coin))); + + final _showAvailable = ref + .watch(walletBalanceToggleStateProvider.state) + .state == + WalletBalanceToggleState.available; + + return FutureBuilder( + future: _showAvailable + ? availableBalanceFuture + : totalBalanceFuture, + builder: (fbContext, AsyncSnapshot<Decimal> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + if (_showAvailable) { + _balanceCached = snapshot.data!; + } else { + _balanceTotalCached = snapshot.data!; + } + } + Decimal? balanceToShow = _showAvailable + ? _balanceCached + : _balanceTotalCached; + + if (balanceToShow != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "${Format.localizedStringAsFixed( + value: balanceToShow, + locale: locale, + decimalPlaces: 8, + )} ${coin.ticker}", + style: STextStyles.desktopH3(context), + ), + ), + if (externalCalls) + Text( + "${Format.localizedStringAsFixed( + value: priceTuple.item1 * balanceToShow, + locale: locale, + decimalPlaces: 2, + )} $baseCurrency", + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textSubtitle1, + ), + ), + ], + ); + } else { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AnimatedText( + stringsToLoopThrough: const [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance..." + ], + style: STextStyles.desktopH3(context).copyWith( + fontSize: 24, + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ), + if (externalCalls) + AnimatedText( + stringsToLoopThrough: const [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance..." + ], + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textSubtitle1, + ), + ), + ], + ); + } + }, + ); }, - ); - }, + ), + ], ), + if (coin == Coin.firo || coin == Coin.firoTestNet) + const SizedBox( + width: 8, + ), + if (coin == Coin.firo || coin == Coin.firoTestNet) + const DesktopBalanceToggleButton(), + const SizedBox( + width: 8, + ), + WalletRefreshButton( + walletId: walletId, + initialSyncStatus: widget.initialSyncStatus, + ) ], - ), - const SizedBox( - width: 8, - ), - WalletRefreshButton( - walletId: walletId, - initialSyncStatus: widget.initialSyncStatus, - ) - ], + ); + }, ); } } diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart index 23360c98c..739a3ebc4 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart @@ -16,6 +16,7 @@ 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/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; class UnlockWalletKeysDesktop extends ConsumerStatefulWidget { @@ -201,11 +202,32 @@ class _UnlockWalletKeysDesktopState enabled: continueEnabled, onPressed: continueEnabled ? () async { + unawaited( + showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future<void>.delayed( + const Duration(seconds: 1)); + final verified = await ref .read(storageCryptoHandlerProvider) .verifyPassphrase(passwordController.text); if (verified) { + Navigator.of(context, rootNavigator: true).pop(); + final words = await ref .read(walletsChangeNotifierProvider) .getManager(widget.walletId) @@ -219,6 +241,11 @@ class _UnlockWalletKeysDesktopState ); } } else { + Navigator.of(context, rootNavigator: true).pop(); + + await Future<void>.delayed( + const Duration(milliseconds: 300)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart index b4ff3fe6a..621683e65 100644 --- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart @@ -8,6 +8,7 @@ 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/custom_buttons/draggable_switch_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'debug_info_dialog.dart'; @@ -143,7 +144,21 @@ class _AdvancedSettings extends ConsumerState<AdvancedSettings> { ), ], ), - const StackPrivacyButton(), + PrimaryButton( + label: "Change", + buttonHeight: ButtonHeight.xs, + width: 86, + onPressed: () async { + await showDialog<dynamic>( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const StackPrivacyDialog(); + }, + ); + }, + ) ], ), ); @@ -172,7 +187,21 @@ class _AdvancedSettings extends ConsumerState<AdvancedSettings> { .textDark), textAlign: TextAlign.left, ), - ShowLogsButton(), + PrimaryButton( + buttonHeight: ButtonHeight.xs, + label: "Show logs", + width: 101, + onPressed: () async { + await showDialog<dynamic>( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const DebugInfoDialog(); + }, + ); + }, + ), ], ), ), @@ -184,81 +213,3 @@ class _AdvancedSettings extends ConsumerState<AdvancedSettings> { ); } } - -class StackPrivacyButton extends ConsumerWidget { - const StackPrivacyButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - Future<void> changePrivacySettings() async { - await showDialog<dynamic>( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackPrivacyDialog(); - }, - ); - } - - return SizedBox( - width: 84, - height: 37, - child: TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - // Navigator.of(context).pushNamed( - // StackPrivacyCalls.routeName, - // arguments: false, - // ); - changePrivacySettings(); - }, - child: Text( - "Change", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith(color: Colors.white), - ), - ), - ); - } -} - -class ShowLogsButton extends ConsumerWidget { - const ShowLogsButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - Future<void> viewDebugLogs() async { - await showDialog<dynamic>( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const DebugInfoDialog(); - }, - ); - } - - return SizedBox( - width: 101, - height: 37, - child: TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - viewDebugLogs(); - }, - child: Text( - "Show logs", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith(color: Colors.white), - ), - ), - ); - } -} diff --git a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart index 7a9ed557f..bfd5f3b61 100644 --- a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart @@ -10,6 +10,7 @@ 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/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -291,7 +292,109 @@ class _ThemeToggle extends ConsumerState<ThemeToggle> { ), ), const SizedBox( - width: 20, + width: 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: [ + Container( + decoration: BoxDecoration( + border: Border.all( + width: 2.5, + color: _selectedTheme == "oceanBreeze" + ? Theme.of(context) + .extension<StackColors>()! + .infoItemIcons + : Theme.of(context).extension<StackColors>()!.popupBG, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: SvgPicture.asset( + Assets.svg.themeOcean, + ), + ), + const SizedBox( + height: 12, + ), + Row( + children: [ + SizedBox( + width: 20, + height: 20, + 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>()! + .textDark, + ), + ), + ], + ), + ], + ), + ), + ), + const SizedBox( + width: 10, ), MaterialButton( splashColor: Colors.transparent, diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 7d70d4d0f..c82b5f923 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -240,7 +240,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: Navigator.of(context).pop, ), @@ -248,7 +248,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { const SizedBox(width: 16), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Disable", onPressed: () { ref @@ -422,7 +422,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { padding: const EdgeInsets.all(10), child: !isEnabledAutoBackup ? PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.m, width: 200, label: "Enable auto backup", onPressed: () { @@ -467,7 +467,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { Row( children: [ PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.m, width: 190, label: "Disable auto backup", onPressed: () { @@ -476,7 +476,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { ), const SizedBox(width: 16), SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.m, width: 190, label: "Edit auto backup", onPressed: () { @@ -560,7 +560,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { child: CreateBackupView(), ) : PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.m, width: 200, label: "Create manual backup", onPressed: () { @@ -642,7 +642,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { child: RestoreFromFileView(), ) : PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.m, width: 200, label: "Restore backup", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index 663136dba..df80da732 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -556,7 +556,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -565,7 +565,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Enable Auto Backup", enabled: shouldEnableCreate, onPressed: !shouldEnableCreate @@ -792,7 +792,8 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> { Expanded( child: PrimaryButton( label: "Ok", - desktopMed: true, + buttonHeight: + ButtonHeight.l, onPressed: () { Navigator.of(context) .pop(); diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart index 6496253d5..df9f18b52 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart @@ -59,7 +59,7 @@ class EnableBackupDialog extends StatelessWidget { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { Navigator.of(context).pop(); @@ -71,7 +71,7 @@ class EnableBackupDialog extends StatelessWidget { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Continue", onPressed: () { Navigator.of(context).pop(); diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index 4c4225ce4..9f5f42b7c 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -107,8 +107,8 @@ class _CurrencySettings extends ConsumerState<CurrencySettings> { 10, ), child: PrimaryButton( - width: 210, - desktopMed: true, + width: 200, + buttonHeight: ButtonHeight.m, enabled: true, label: "Change currency", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart index 08aeb9bc3..3c511236c 100644 --- a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart @@ -80,12 +80,12 @@ class _LanguageOptionSettings extends ConsumerState<LanguageOptionSettings> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.all( + padding: const EdgeInsets.all( 10, ), child: PrimaryButton( - width: 210, - desktopMed: true, + width: 200, + buttonHeight: ButtonHeight.m, enabled: true, label: "Change language", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index 9f870440b..ff7537126 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -61,6 +61,8 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> { if (verified) { if (pwNew != pwNewRepeat) { + await Future<void>.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -77,6 +79,8 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> { ); if (success) { + await Future<void>.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.success, @@ -86,6 +90,8 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> { ); return true; } else { + await Future<void>.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -97,6 +103,8 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> { } } } else { + await Future<void>.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -485,7 +493,7 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> { const SizedBox(height: 20), PrimaryButton( width: 160, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: shouldEnableSave, label: "Save changes", onPressed: () async { @@ -503,7 +511,7 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> { ) : PrimaryButton( width: 210, - desktopMed: true, + buttonHeight: ButtonHeight.m, enabled: true, label: "Set up new password", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index 408b93e15..720d77b8b 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -3,9 +3,13 @@ import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class SyncingPreferencesSettings extends ConsumerStatefulWidget { @@ -20,6 +24,19 @@ class SyncingPreferencesSettings extends ConsumerStatefulWidget { class _SyncingPreferencesSettings extends ConsumerState<SyncingPreferencesSettings> { + String _currentTypeDescription(SyncingType type) { + switch (type) { + case SyncingType.currentWalletOnly: + return "Sync only currently open wallet"; + case SyncingType.selectedWalletsAtStartup: + return "Sync only selected wallets at startup"; + case SyncingType.allWalletsOnStartup: + return "Sync all wallets at startup"; + } + } + + late bool changePrefs = false; + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); @@ -34,13 +51,40 @@ class _SyncingPreferencesSettings child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - Assets.svg.circleArrowRotate, - width: 48, - height: 48, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleArrowRotate, + width: 48, + height: 48, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: RoundedContainer( + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondaryDisabled, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _currentTypeDescription(ref.watch( + prefsChangeNotifierProvider + .select((value) => value.syncType))), + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark2), + textAlign: TextAlign.left, + ), + ), + ), + ), + ], ), Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -67,28 +111,50 @@ class _SyncingPreferencesSettings ), ], ), - - ///TODO: ONLY SHOW SYNC OPTIONS ON BUTTON PRESS - Column( - children: const [ - SyncingOptionsView(), - ], - ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.all( - 10, - ), - child: PrimaryButton( - width: 210, - desktopMed: true, - enabled: true, - label: "Change preferences", - onPressed: () {}, - ), - ), + padding: const EdgeInsets.all( + 10, + ), + child: changePrefs + ? SizedBox( + width: 512, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SyncingOptionsView(), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.m, + enabled: true, + label: "Save", + onPressed: () { + setState(() { + changePrefs = false; + }); + }, + ), + ], + ), + ) + : Column( + children: [ + const SizedBox(height: 10), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.m, + enabled: true, + label: "Change preferences", + onPressed: () { + setState(() { + changePrefs = true; + }); + }, + ), + ], + )), ], ), ], diff --git a/lib/route_generator.dart b/lib/route_generator.dart index d7865d013..cbc4cb343 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -85,12 +85,16 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_notifications_view.dart'; @@ -1019,6 +1023,12 @@ class RouteGenerator { builder: (_) => const DesktopNotificationsView(), settings: RouteSettings(name: settings.name)); + case DesktopExchangeView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopExchangeView(), + settings: RouteSettings(name: settings.name)); + case DesktopSettingsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -1163,6 +1173,73 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopDeleteWalletDialog.routeName: + if (args is String) { + return FadePageRoute( + DesktopDeleteWalletDialog( + walletId: args, + ), + RouteSettings( + name: settings.name, + ), + ); + // return getRoute( + // shouldUseMaterialRoute: useMaterialPageRoute, + // builder: (_) => WalletKeysDesktopPopup( + // words: args, + // ), + // settings: RouteSettings( + // name: settings.name, + // ), + // ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case DesktopAttentionDeleteWallet.routeName: + if (args is String) { + return FadePageRoute( + DesktopAttentionDeleteWallet( + walletId: args, + ), + RouteSettings( + name: settings.name, + ), + ); + // return getRoute( + // shouldUseMaterialRoute: useMaterialPageRoute, + // builder: (_) => WalletKeysDesktopPopup( + // words: args, + // ), + // settings: RouteSettings( + // name: settings.name, + // ), + // ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case DeleteWalletKeysPopup.routeName: + if (args is Tuple2<String, List<String>>) { + return FadePageRoute( + DeleteWalletKeysPopup( + walletId: args.item1, + words: args.item2, + ), + RouteSettings( + name: settings.name, + ), + ); + // return getRoute( + // shouldUseMaterialRoute: useMaterialPageRoute, + // builder: (_) => WalletKeysDesktopPopup( + // words: args, + // ), + // settings: RouteSettings( + // name: settings.name, + // ), + // ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case QRCodeDesktopPopupContent.routeName: if (args is String) { return FadePageRoute( diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index c35323d53..f94f0cd2a 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -185,8 +185,8 @@ class MoneroWallet extends CoinServiceAPI { try { if (walletBase!.syncStatus! is SyncedSyncStatus && walletBase!.syncStatus!.progress() == 1.0) { - Logging.instance - .log("currentSyncingHeight lol", level: LogLevel.Warning); + // Logging.instance + // .log("currentSyncingHeight lol", level: LogLevel.Warning); return getSyncingHeight(); } } catch (e, s) {} diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index e39d13005..e6a531b78 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -153,7 +153,7 @@ class WowneroWallet extends CoinServiceAPI { try { _height = (walletBase!.syncStatus as SyncingSyncStatus).height; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } int blocksRemaining = -1; @@ -162,7 +162,7 @@ class WowneroWallet extends CoinServiceAPI { blocksRemaining = (walletBase!.syncStatus as SyncingSyncStatus).blocksLeft; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } int currentHeight = _height + blocksRemaining; if (_height == -1 || blocksRemaining == -1) { @@ -186,8 +186,8 @@ class WowneroWallet extends CoinServiceAPI { try { if (walletBase!.syncStatus! is SyncedSyncStatus && walletBase!.syncStatus!.progress() == 1.0) { - Logging.instance - .log("currentSyncingHeight lol", level: LogLevel.Warning); + // Logging.instance + // .log("currentSyncingHeight lol", level: LogLevel.Warning); return getSyncingHeight(); } } catch (e, s) {} @@ -195,7 +195,7 @@ class WowneroWallet extends CoinServiceAPI { try { syncingHeight = (walletBase!.syncStatus as SyncingSyncStatus).height; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } final cachedHeight = DB.instance.get<dynamic>(boxName: walletId, key: "storedSyncingHeight") @@ -418,7 +418,7 @@ class WowneroWallet extends CoinServiceAPI { try { progress = (walletBase!.syncStatus!).progress(); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } await _fetchTransactionData(); @@ -863,7 +863,8 @@ class WowneroWallet extends CoinServiceAPI { await DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain(0, curIndex); - Logging.instance.log("xmr address in init existing: $newReceivingAddress", + Logging.instance.log( + "wownero address in init existing: $newReceivingAddress", level: LogLevel.Info); _currentReceivingAddress = Future(() => newReceivingAddress); } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 38d720969..149d46b3c 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -59,12 +59,14 @@ class _SVG { String txExchangeFailed(BuildContext context) => "assets/svg/${Theme.of(context).extension<StackColors>()!.themeType.name}/tx-exchange-icon-failed.svg"; + String get themeOcean => "assets/svg/ocean-breeze-theme.svg"; + String get themeLight => "assets/svg/light-mode.svg"; + String get themeDark => "assets/svg/dark-theme.svg"; + String get circleSliders => "assets/svg/configuration.svg"; String get circlePlus => "assets/svg/plus-circle.svg"; String get framedGear => "assets/svg/framed-gear.svg"; String get framedAddressBook => "assets/svg/framed-address-book.svg"; - String get themeLight => "assets/svg/light/light-mode.svg"; - String get themeDark => "assets/svg/dark/dark-theme.svg"; String get circleNode => "assets/svg/node-circle.svg"; String get circleSun => "assets/svg/sun-circle.svg"; String get circleArrowRotate => "assets/svg/rotate-circle.svg"; @@ -105,6 +107,7 @@ class _SVG { String get swap => "assets/svg/swap.svg"; String get downloadFolder => "assets/svg/folder-down.svg"; String get lock => "assets/svg/lock-keyhole.svg"; + String get lockOpen => "assets/svg/lock-open.svg"; String get network => "assets/svg/network-wired.svg"; String get networkWired => "assets/svg/network-wired-2.svg"; String get addressBook => "assets/svg/address-book.svg"; @@ -228,6 +231,9 @@ class _PNG { String get bitcoincash => "assets/images/bitcoincash.png"; String get namecoin => "assets/images/namecoin.png"; + String get glasses => "assets/images/glasses.png"; + String get glassesHidden => "assets/images/glasses-hidden.png"; + String imageFor({required Coin coin}) { switch (coin) { case Coin.bitcoin: diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index e27fbaa3d..0a062de67 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -40,6 +40,30 @@ abstract class Constants { static const int currentHiveDbVersion = 3; + static int decimalPlacesForCoin(Coin coin) { + switch (coin) { + case Coin.bitcoin: + case Coin.litecoin: + case Coin.litecoinTestNet: + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + case Coin.dogecoin: + case Coin.firo: + case Coin.bitcoinTestNet: + case Coin.dogecoinTestNet: + case Coin.firoTestNet: + case Coin.epicCash: + case Coin.namecoin: + return decimalPlaces; + + case Coin.wownero: + return decimalPlacesWownero; + + case Coin.monero: + return decimalPlacesMonero; + } + } + static List<int> possibleLengthsForCoin(Coin coin) { final List<int> values = []; switch (coin) { diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 48212bde8..543a193ee 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -12,6 +12,7 @@ import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' as nmc; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; +import 'package:stackwallet/utilities/util.dart'; enum Coin { bitcoin, @@ -36,8 +37,7 @@ enum Coin { firoTestNet, } -// remove firotestnet for now -const int kTestNetCoinCount = 4; +final int kTestNetCoinCount = Util.isDesktop ? 5 : 4; extension CoinExt on Coin { String get prettyName { diff --git a/lib/utilities/text_styles.dart b/lib/utilities/text_styles.dart index 63aa19afb..c9dd15e1d 100644 --- a/lib/utilities/text_styles.dart +++ b/lib/utilities/text_styles.dart @@ -15,6 +15,12 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -32,6 +38,12 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 18, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 18, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -49,6 +61,12 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -66,6 +84,12 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -83,6 +107,12 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -100,6 +130,12 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -117,6 +153,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -134,6 +176,12 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -151,6 +199,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimary, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, @@ -168,6 +222,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -185,6 +245,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark3, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark3, @@ -202,6 +268,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark3, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark3, @@ -219,6 +291,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -237,6 +315,13 @@ class STextStyles { fontSize: 14, height: 14 / 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textFieldActiveSearchIconRight, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 14 / 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textFieldActiveSearchIconRight, @@ -255,6 +340,12 @@ class STextStyles { fontWeight: FontWeight.w700, fontSize: 12, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w700, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -272,6 +363,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).infoItemLabel, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).infoItemLabel, @@ -289,6 +386,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -306,6 +409,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -324,6 +433,13 @@ class STextStyles { fontSize: 14, height: 1.5, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle2, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle2, @@ -343,6 +459,13 @@ class STextStyles { fontSize: 14, height: 1.5, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -361,6 +484,12 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -378,6 +507,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).accentColorRed, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).accentColorRed, @@ -395,6 +530,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).infoItemIcons, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).infoItemIcons, @@ -412,6 +553,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).accentColorBlue, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).accentColorBlue, @@ -429,6 +576,12 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 12, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -446,6 +599,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -463,6 +622,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -480,6 +645,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 10, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textError, + fontWeight: FontWeight.w500, + fontSize: 10, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textError, @@ -497,6 +668,12 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 10, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w500, + fontSize: 10, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -517,6 +694,13 @@ class STextStyles { fontSize: 40, height: 40 / 40, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 40, + height: 40 / 40, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -536,6 +720,13 @@ class STextStyles { fontSize: 32, height: 32 / 32, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 32, + height: 32 / 32, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -555,6 +746,13 @@ class STextStyles { fontSize: 24, height: 24 / 24, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 24, + height: 24 / 24, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -574,6 +772,13 @@ class STextStyles { fontSize: 20, height: 30 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 30 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -593,6 +798,13 @@ class STextStyles { fontSize: 20, height: 30 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 20, + height: 30 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -612,6 +824,13 @@ class STextStyles { fontSize: 20, height: 28 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 20, + height: 28 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -631,6 +850,13 @@ class STextStyles { fontSize: 24, height: 33 / 24, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 24, + height: 33 / 24, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -650,6 +876,13 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimary, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, @@ -669,6 +902,13 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimaryDisabled, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -688,6 +928,13 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextSecondary, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, @@ -707,6 +954,13 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextSecondaryDisabled, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondaryDisabled, @@ -726,6 +980,13 @@ class STextStyles { fontSize: 18, height: 27 / 18, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 18, + height: 27 / 18, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -745,6 +1006,13 @@ class STextStyles { fontSize: 16, height: 24 / 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimaryDisabled, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 24 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -764,6 +1032,13 @@ class STextStyles { fontSize: 14, height: 21 / 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 21 / 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -783,6 +1058,13 @@ class STextStyles { fontSize: 14, height: 21 / 14, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 14, + height: 21 / 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -802,6 +1084,13 @@ class STextStyles { fontSize: 16, height: 24 / 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextSecondary, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 24 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, @@ -821,6 +1110,13 @@ class STextStyles { fontSize: 20, height: 30 / 20, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle2, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 30 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle2, @@ -840,6 +1136,13 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark.withOpacity(0.8), + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.8), @@ -859,6 +1162,13 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -878,6 +1188,13 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark.withOpacity(0.5), + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.5), @@ -897,6 +1214,13 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -915,6 +1239,12 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 8, ); + case ThemeType.oceanBreeze: + return GoogleFonts.roboto( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 8, + ); case ThemeType.dark: return GoogleFonts.roboto( color: _theme(context).textDark, @@ -932,6 +1262,12 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 26, ); + case ThemeType.oceanBreeze: + return GoogleFonts.roboto( + color: _theme(context).numberTextDefault, + fontWeight: FontWeight.w400, + fontSize: 26, + ); case ThemeType.dark: return GoogleFonts.roboto( color: _theme(context).numberTextDefault, @@ -950,6 +1286,13 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 12, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + letterSpacing: 0.5, + color: _theme(context).accentColorDark, + fontWeight: FontWeight.w400, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( letterSpacing: 0.5, @@ -969,6 +1312,13 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + letterSpacing: 0.5, + color: _theme(context).accentColorDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( letterSpacing: 0.5, diff --git a/lib/utilities/theme/color_theme.dart b/lib/utilities/theme/color_theme.dart index 49fd41a6e..852e2f586 100644 --- a/lib/utilities/theme/color_theme.dart +++ b/lib/utilities/theme/color_theme.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; enum ThemeType { light, dark, + oceanBreeze, } abstract class StackColorTheme { diff --git a/lib/utilities/theme/light_colors.dart b/lib/utilities/theme/light_colors.dart index ea3a7cb92..896ae4e5e 100644 --- a/lib/utilities/theme/light_colors.dart +++ b/lib/utilities/theme/light_colors.dart @@ -11,7 +11,7 @@ class LightColors extends StackColorTheme { Color get overlay => const Color(0xFF111215); @override - Color get accentColorBlue => const Color(0xFF4C86E9); + Color get accentColorBlue => const Color(0xFF0052DF); @override Color get accentColorGreen => const Color(0xFF4CC0A0); @override diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart new file mode 100644 index 000000000..665eaa0c3 --- /dev/null +++ b/lib/utilities/theme/ocean_breeze_colors.dart @@ -0,0 +1,306 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/theme/color_theme.dart'; + +class OceanBreezeColors extends StackColorTheme { + @override + ThemeType get themeType => ThemeType.oceanBreeze; + + @override + Color get background => const Color(0xFFF3F7FA); + @override + Color get overlay => const Color(0xFF111215); + + @override + Color get accentColorBlue => const Color(0xFF077CBE); + @override + Color get accentColorGreen => const Color(0xFF00A591); + @override + Color get accentColorYellow => const Color(0xFFF4C517); + @override + Color get accentColorRed => const Color(0xFFD1382D); + @override + Color get accentColorOrange => const Color(0xFFFF985F); + @override + Color get accentColorDark => const Color(0xFF227386); + + @override + Color get shadow => const Color(0x0F2D3132); + + @override + Color get textDark => const Color(0xFF232323); + @override + Color get textDark2 => const Color(0xFF333333); + @override + Color get textDark3 => const Color(0xFF696B6C); + @override + Color get textSubtitle1 => const Color(0xFF7E8284); + @override + Color get textSubtitle2 => const Color(0xFF919393); + @override + Color get textSubtitle3 => const Color(0xFFB0B2B2); + @override + Color get textSubtitle4 => const Color(0xFFD1D3D3); + @override + Color get textSubtitle5 => const Color(0xFFDEDFE1); + @override + Color get textSubtitle6 => const Color(0xFFF1F1F1); + @override + Color get textWhite => const Color(0xFFFFFFFF); + @override + Color get textFavoriteCard => const Color(0xFF232323); + @override + Color get textError => const Color(0xFF8D0006); + + // button background + @override + Color get buttonBackPrimary => const Color(0xFF227386); + @override + Color get buttonBackSecondary => const Color(0xFFC2DAE2); + @override + Color get buttonBackPrimaryDisabled => const Color(0xFFBDD5DB); + @override + Color get buttonBackSecondaryDisabled => const Color(0xFFBDBDBD); + @override + Color get buttonBackBorder => const Color(0xFF227386); + @override + Color get buttonBackBorderDisabled => const Color(0xFFBDD5DB); + + @override + Color get numberBackDefault => const Color(0xFFFFFFFF); + @override + Color get numpadBackDefault => const Color(0xFF227386); + @override + Color get bottomNavBack => const Color(0xFFFFFFFF); + + // button text/element + @override + Color get buttonTextPrimary => const Color(0xFFFFFFFF); + @override + Color get buttonTextSecondary => const Color(0xFF232323); + @override + Color get buttonTextPrimaryDisabled => const Color(0xFFFFFFFF); + @override + Color get buttonTextSecondaryDisabled => const Color(0xFFBDD5DB); + @override + Color get buttonTextBorder => const Color(0xFF227386); + @override + Color get buttonTextDisabled => const Color(0xFFFFFFFF); + @override + Color get buttonTextBorderless => const Color(0xFF056EC6); + @override + Color get buttonTextBorderlessDisabled => const Color(0xFFB6B6B6); + @override + Color get numberTextDefault => const Color(0xFF232323); + @override + Color get numpadTextDefault => const Color(0xFFFFFFFF); + @override + Color get bottomNavText => const Color(0xFF232323); + + // switch + @override + Color get switchBGOn => const Color(0xFF056EC6); + @override + Color get switchBGOff => const Color(0xFFCCDBF9); + @override + Color get switchBGDisabled => const Color(0xFFC5C6C9); + @override + Color get switchCircleOn => const Color(0xFFDAE2FF); + @override + Color get switchCircleOff => const Color(0xFFFBFCFF); + @override + Color get switchCircleDisabled => const Color(0xFFFBFCFF); + + // step indicator background + @override + Color get stepIndicatorBGCheck => const Color(0xFFCDD9FF); + @override + Color get stepIndicatorBGNumber => const Color(0xFFCDD9FF); + @override + Color get stepIndicatorBGInactive => const Color(0xFFA6C7D1); + @override + Color get stepIndicatorBGLines => const Color(0xFF90B8DC); + @override + Color get stepIndicatorBGLinesInactive => const Color(0xFFBCD4EA); + @override + Color get stepIndicatorIconText => const Color(0xFF005BAF); + @override + Color get stepIndicatorIconNumber => const Color(0xFF005BAF); + @override + Color get stepIndicatorIconInactive => const Color(0xFFD4DFFF); + + // checkbox + @override + Color get checkboxBGChecked => const Color(0xFF056EC6); + @override + Color get checkboxBorderEmpty => const Color(0xFF8C8F90); + @override + Color get checkboxBGDisabled => const Color(0xFFB0C9ED); + @override + Color get checkboxIconChecked => const Color(0xFFFFFFFF); + @override + Color get checkboxIconDisabled => const Color(0xFFFFFFFF); + @override + Color get checkboxTextLabel => const Color(0xFF232323); + + // snack bar + @override + Color get snackBarBackSuccess => const Color(0xFFADD6D2); + @override + Color get snackBarBackError => const Color(0xFFF6C7C3); + @override + Color get snackBarBackInfo => const Color(0xFFCCD7FF); + @override + Color get snackBarTextSuccess => const Color(0xFF075547); + @override + Color get snackBarTextError => const Color(0xFF8D0006); + @override + Color get snackBarTextInfo => const Color(0xFF002569); + + // icons + @override + Color get bottomNavIconBack => const Color(0xFFA7C7CF); + @override + Color get bottomNavIconIcon => const Color(0xFF227386); + + @override + Color get topNavIconPrimary => const Color(0xFF227386); + @override + Color get topNavIconGreen => const Color(0xFF00A591); + @override + Color get topNavIconYellow => const Color(0xFFFDD33A); + @override + Color get topNavIconRed => const Color(0xFFEA4649); + + @override + Color get settingsIconBack => const Color(0xFFE0E3E3); + @override + Color get settingsIconIcon => const Color(0xFF232323); + @override + Color get settingsIconBack2 => const Color(0xFF80D2C8); + @override + Color get settingsIconElement => const Color(0xFF00A591); + + // text field + @override + Color get textFieldActiveBG => const Color(0xFFD3E3E7); + @override + Color get textFieldDefaultBG => const Color(0xFFD8E7EB); + @override + Color get textFieldErrorBG => const Color(0xFFF6C7C3); + @override + Color get textFieldSuccessBG => const Color(0xFFADD6D2); + + @override + Color get textFieldActiveSearchIconLeft => const Color(0xFF86898C); + @override + Color get textFieldDefaultSearchIconLeft => const Color(0xFF86898C); + @override + Color get textFieldErrorSearchIconLeft => const Color(0xFF8D0006); + @override + Color get textFieldSuccessSearchIconLeft => const Color(0xFF006C4D); + + @override + Color get textFieldActiveText => const Color(0xFF232323); + @override + Color get textFieldDefaultText => const Color(0xFF86898C); + @override + Color get textFieldErrorText => const Color(0xFF000000); + @override + Color get textFieldSuccessText => const Color(0xFF000000); + + @override + Color get textFieldActiveLabel => const Color(0xFF86898C); + @override + Color get textFieldErrorLabel => const Color(0xFF8D0006); + @override + Color get textFieldSuccessLabel => const Color(0xFF077C6E); + + @override + Color get textFieldActiveSearchIconRight => const Color(0xFF388192); + @override + Color get textFieldDefaultSearchIconRight => const Color(0xFF388192); + @override + Color get textFieldErrorSearchIconRight => const Color(0xFF8D0006); + @override + Color get textFieldSuccessSearchIconRight => const Color(0xFF077C6E); + + // settings item level2 + @override + Color get settingsItem2ActiveBG => const Color(0xFFFFFFFF); + @override + Color get settingsItem2ActiveText => const Color(0xFF232323); + @override + Color get settingsItem2ActiveSub => const Color(0xFF8C8F90); + + // radio buttons + @override + Color get radioButtonIconBorder => const Color(0xFF056EC6); + @override + Color get radioButtonIconBorderDisabled => const Color(0xFF8C8D97); + @override + Color get radioButtonBorderEnabled => const Color(0xFF056EC6); + @override + Color get radioButtonBorderDisabled => const Color(0xFF8C8D97); + @override + Color get radioButtonIconCircle => const Color(0xFF056EC6); + @override + Color get radioButtonIconEnabled => const Color(0xFF056EC6); + @override + Color get radioButtonTextEnabled => const Color(0xFF42444B); + @override + Color get radioButtonTextDisabled => const Color(0xFF42444B); + @override + Color get radioButtonLabelEnabled => const Color(0xFF8C8F90); + @override + Color get radioButtonLabelDisabled => const Color(0xFF8C8F90); + + // info text + @override + Color get infoItemBG => const Color(0xFFFFFFFF); + @override + Color get infoItemLabel => const Color(0xFF838788); + @override + Color get infoItemText => const Color(0xFF232323); + @override + Color get infoItemIcons => const Color(0xFF056EC6); + + // popup + @override + Color get popupBG => const Color(0xFFFFFFFF); + + // currency list + @override + Color get currencyListItemBG => const Color(0xFFF0F5F7); + + // bottom nav + @override + Color get stackWalletBG => const Color(0xFFFFFFFF); + @override + Color get stackWalletMid => const Color(0xFFFFFFFF); + @override + Color get stackWalletBottom => const Color(0xFF232323); + @override + Color get bottomNavShadow => const Color(0xFF388192); + + @override + Color get favoriteStarActive => const Color(0xFFF4C517); + @override + Color get favoriteStarInactive => const Color(0xFFB0B2B2); + + @override + Color get splash => const Color(0xFF8E9192); + @override + Color get highlight => const Color(0xFFA9ACAC); + @override + Color get warningForeground => const Color(0xFF232323); + @override + Color get warningBackground => const Color(0xFFF6C7C3); + @override + Color get loadingOverlayTextColor => const Color(0xFFF7F7F7); + @override + Color get myStackContactIconBG => const Color(0xFFD8E7EB); + @override + Color get textConfirmTotalAmount => const Color(0xFF232323); + @override + Color get textSelectedWordTableItem => const Color(0xFF232323); +} diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index c9ac86052..dfa655f86 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -9,6 +10,8 @@ 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/conditional_parent.dart'; +import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class AddressBookCard extends ConsumerStatefulWidget { @@ -16,10 +19,12 @@ class AddressBookCard extends ConsumerStatefulWidget { Key? key, required this.contactId, this.indicatorDown, + this.desktopSendFrom = true, }) : super(key: key); final String contactId; - final bool? indicatorDown; + final ExpandableState? indicatorDown; + final bool desktopSendFrom; @override ConsumerState<AddressBookCard> createState() => _AddressBookCardState(); @@ -28,20 +33,28 @@ class AddressBookCard extends ConsumerStatefulWidget { class _AddressBookCardState extends ConsumerState<AddressBookCard> { late final String contactId; late final bool isDesktop; + late final bool desktopSendFrom; @override void initState() { contactId = widget.contactId; + desktopSendFrom = widget.desktopSendFrom; isDesktop = Util.isDesktop; super.initState(); } @override Widget build(BuildContext context) { - // final isTiny = SizingUtilities.isTinyWidth(context); + // provider hack to prevent trying to update widget with deleted contact + Contact? _contact; + try { + _contact = ref.watch(addressBookServiceProvider + .select((value) => value.getContactById(contactId))); + } catch (_) { + return Container(); + } - final contact = ref.watch(addressBookServiceProvider - .select((value) => value.getContactById(contactId))); + final contact = _contact!; final List<Coin> coins = []; for (var element in contact.addresses) { @@ -58,108 +71,112 @@ class _AddressBookCardState extends ConsumerState<AddressBookCard> { } } - return RoundedWhiteContainer( - padding: const EdgeInsets.all(4), - 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: () { - showDialog<void>( - context: context, - useSafeArea: true, - barrierDismissible: true, - builder: (_) => ContactPopUp( - contactId: contact.id, + return ConditionalParent( + condition: !isDesktop, + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: contact.id == "default" + ? Theme.of(context) + .extension<StackColors>()! + .myStackContactIconBG + : Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(32), ), - ); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: contact.id == "default" - ? Theme.of(context) - .extension<StackColors>()! - .myStackContactIconBG - : Theme.of(context) - .extension<StackColors>()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular(32), - ), - child: contact.id == "default" + child: contact.id == "default" + ? Center( + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 20, + ), + ) + : contact.emojiChar != null ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), - width: 20, - ), + child: Text(contact.emojiChar!), ) - : contact.emojiChar != null - ? Center( - child: Text(contact.emojiChar!), - ) - : Center( - child: SvgPicture.asset( - Assets.svg.user, - width: 18, - ), - ), - ), - const SizedBox( - width: 12, - ), - if (isDesktop) + : Center( + child: SvgPicture.asset( + Assets.svg.user, + width: 18, + ), + ), + ), + const SizedBox( + width: 12, + ), + if (isDesktop) + Text( + contact.name, + style: STextStyles.itemSubtitle12(context), + ), + if (isDesktop) + const SizedBox( + width: 16, + ), + if (isDesktop && !desktopSendFrom) const Spacer(), + if (isDesktop) + Text( + coinsString, + style: STextStyles.label(context), + ), + if (!isDesktop) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( contact.name, style: STextStyles.itemSubtitle12(context), ), - if (isDesktop) const SizedBox( - width: 16, + height: 4, ), - if (isDesktop) Text( coinsString, style: STextStyles.label(context), ), - if (!isDesktop) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - contact.name, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 4, - ), - Text( - coinsString, - style: STextStyles.label(context), - ), - ], - ), - if (isDesktop) const Spacer(), - // if (isDesktop) - // SvgPicture.asset( - // widget.indicatorDown == true - // ? Assets.svg.chevronDown - // : Assets.svg.chevronUp, - // width: 10, - // height: 5, - // color: - // Theme.of(context).extension<StackColors>()!.textSubtitle2, - // ), - ], + ], + ), + if (isDesktop && desktopSendFrom) const Spacer(), + if (isDesktop && desktopSendFrom) + SvgPicture.asset( + widget.indicatorDown == ExpandableState.collapsed + ? Assets.svg.chevronDown + : Assets.svg.chevronUp, + width: 10, + height: 5, + color: Theme.of(context).extension<StackColors>()!.textSubtitle2, + ), + ], + ), + builder: (child) => RoundedWhiteContainer( + padding: const EdgeInsets.all(4), + 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: () { + showDialog<void>( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => ContactPopUp( + contactId: contact.id, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: child, ), ), ), diff --git a/lib/widgets/desktop/custom_text_button.dart b/lib/widgets/desktop/custom_text_button.dart index b96a697b8..90b75c459 100644 --- a/lib/widgets/desktop/custom_text_button.dart +++ b/lib/widgets/desktop/custom_text_button.dart @@ -1,6 +1,16 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/util.dart'; +enum ButtonHeight { + xxs, + xs, + s, + m, + l, + xl, + xxl, +} + class CustomTextButtonBase extends StatelessWidget { const CustomTextButtonBase({ Key? key, diff --git a/lib/widgets/desktop/primary_button.dart b/lib/widgets/desktop/primary_button.dart index f3c900c34..9441168e7 100644 --- a/lib/widgets/desktop/primary_button.dart +++ b/lib/widgets/desktop/primary_button.dart @@ -4,6 +4,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; +export 'package:stackwallet/widgets/desktop/custom_text_button.dart'; + class PrimaryButton extends StatelessWidget { const PrimaryButton({ Key? key, @@ -13,7 +15,7 @@ class PrimaryButton extends StatelessWidget { this.icon, this.onPressed, this.enabled = true, - this.desktopMed = false, + this.buttonHeight, }) : super(key: key); final double? width; @@ -22,23 +24,44 @@ class PrimaryButton extends StatelessWidget { final VoidCallback? onPressed; final bool enabled; final Widget? icon; - final bool desktopMed; + final ButtonHeight? buttonHeight; TextStyle getStyle(bool isDesktop, BuildContext context) { if (isDesktop) { - if (desktopMed) { - return STextStyles.desktopTextExtraSmall(context).copyWith( - color: enabled - ? Theme.of(context).extension<StackColors>()!.buttonTextPrimary - : Theme.of(context) - .extension<StackColors>()! - .buttonTextPrimaryDisabled, - ); - } else { + if (buttonHeight == null) { return enabled ? STextStyles.desktopButtonEnabled(context) : STextStyles.desktopButtonDisabled(context); } + + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + return STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context).extension<StackColors>()!.buttonTextPrimary + : Theme.of(context) + .extension<StackColors>()! + .buttonTextPrimaryDisabled, + ); + + case ButtonHeight.m: + case ButtonHeight.l: + return STextStyles.desktopTextExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context).extension<StackColors>()!.buttonTextPrimary + : Theme.of(context) + .extension<StackColors>()! + .buttonTextPrimaryDisabled, + ); + + case ButtonHeight.xl: + case ButtonHeight.xxl: + return enabled + ? STextStyles.desktopButtonEnabled(context) + : STextStyles.desktopButtonDisabled(context); + } } else { return STextStyles.button(context).copyWith( color: enabled @@ -50,12 +73,51 @@ class PrimaryButton extends StatelessWidget { } } + double? _getHeight() { + if (buttonHeight == null) { + return height; + } + + if (Util.isDesktop) { + switch (buttonHeight!) { + case ButtonHeight.xxs: + return 32; + case ButtonHeight.xs: + return 37; + case ButtonHeight.s: + return 40; + case ButtonHeight.m: + return 48; + case ButtonHeight.l: + return 56; + case ButtonHeight.xl: + return 70; + case ButtonHeight.xxl: + return 96; + } + } else { + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + case ButtonHeight.m: + return 28; + case ButtonHeight.l: + return 30; + case ButtonHeight.xl: + return 46; + case ButtonHeight.xxl: + return 56; + } + } + } + @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; return CustomTextButtonBase( - height: desktopMed ? 56 : height, + height: _getHeight(), width: width, textButton: TextButton( onPressed: enabled ? onPressed : null, diff --git a/lib/widgets/desktop/secondary_button.dart b/lib/widgets/desktop/secondary_button.dart index 8d5eae0ce..62bd900dd 100644 --- a/lib/widgets/desktop/secondary_button.dart +++ b/lib/widgets/desktop/secondary_button.dart @@ -4,6 +4,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; +export 'package:stackwallet/widgets/desktop/custom_text_button.dart'; + class SecondaryButton extends StatelessWidget { const SecondaryButton({ Key? key, @@ -13,7 +15,7 @@ class SecondaryButton extends StatelessWidget { this.icon, this.onPressed, this.enabled = true, - this.desktopMed = false, + this.buttonHeight, }) : super(key: key); final double? width; @@ -22,23 +24,47 @@ class SecondaryButton extends StatelessWidget { final VoidCallback? onPressed; final bool enabled; final Widget? icon; - final bool desktopMed; + final ButtonHeight? buttonHeight; TextStyle getStyle(bool isDesktop, BuildContext context) { if (isDesktop) { - if (desktopMed) { - return STextStyles.desktopTextExtraSmall(context).copyWith( - color: enabled - ? Theme.of(context).extension<StackColors>()!.buttonTextSecondary - : Theme.of(context) - .extension<StackColors>()! - .buttonTextSecondaryDisabled, - ); - } else { + if (buttonHeight == null) { return enabled ? STextStyles.desktopButtonSecondaryEnabled(context) : STextStyles.desktopButtonSecondaryDisabled(context); } + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + return STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondary + : Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondaryDisabled, + ); + + case ButtonHeight.m: + case ButtonHeight.l: + return STextStyles.desktopTextExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondary + : Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondaryDisabled, + ); + + case ButtonHeight.xl: + case ButtonHeight.xxl: + return enabled + ? STextStyles.desktopButtonSecondaryEnabled(context) + : STextStyles.desktopButtonSecondaryDisabled(context); + } } else { return STextStyles.button(context).copyWith( color: enabled @@ -50,12 +76,51 @@ class SecondaryButton extends StatelessWidget { } } + double? _getHeight() { + if (buttonHeight == null) { + return height; + } + + if (Util.isDesktop) { + switch (buttonHeight!) { + case ButtonHeight.xxs: + return 32; + case ButtonHeight.xs: + return 37; + case ButtonHeight.s: + return 40; + case ButtonHeight.m: + return 48; + case ButtonHeight.l: + return 56; + case ButtonHeight.xl: + return 70; + case ButtonHeight.xxl: + return 96; + } + } else { + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + case ButtonHeight.m: + return 28; + case ButtonHeight.l: + return 30; + case ButtonHeight.xl: + return 46; + case ButtonHeight.xxl: + return 56; + } + } + } + @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; return CustomTextButtonBase( - height: desktopMed ? 56 : height, + height: _getHeight(), width: width, textButton: TextButton( onPressed: enabled ? onPressed : null, diff --git a/lib/widgets/desktop/simple_desktop_dialog.dart b/lib/widgets/desktop/simple_desktop_dialog.dart new file mode 100644 index 000000000..cd066c221 --- /dev/null +++ b/lib/widgets/desktop/simple_desktop_dialog.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.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'; + +class SimpleDesktopDialog extends StatelessWidget { + const SimpleDesktopDialog({ + Key? key, + required this.title, + required this.message, + }) : super(key: key); + + final String title; + final String message; + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 500, + maxHeight: 300, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + const Spacer(), + Text( + message, + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/widgets/emoji_select_sheet.dart b/lib/widgets/emoji_select_sheet.dart index 85a90fec8..7bf02e967 100644 --- a/lib/widgets/emoji_select_sheet.dart +++ b/lib/widgets/emoji_select_sheet.dart @@ -4,6 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.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/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; class EmojiSelectSheet extends ConsumerWidget { const EmojiSelectSheet({ @@ -16,7 +19,9 @@ class EmojiSelectSheet extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final size = MediaQuery.of(context).size; + final isDesktop = Util.isDesktop; + + final size = isDesktop ? const Size(600, 700) : MediaQuery.of(context).size; final double maxHeight = size.height * 0.60; final double availableWidth = size.width - (2 * horizontalPadding); final int emojisPerRow = @@ -24,90 +29,115 @@ class EmojiSelectSheet extends ConsumerWidget { final itemCount = Emoji.all().length; - return Container( - decoration: BoxDecoration( - color: Theme.of(context).extension<StackColors>()!.popupBG, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(20), + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Container( + decoration: BoxDecoration( + color: Theme.of(context).extension<StackColors>()!.popupBG, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + child: LimitedBox( + maxHeight: maxHeight, + child: Padding( + padding: EdgeInsets.only( + left: horizontalPadding, + right: horizontalPadding, + top: 10, + bottom: 0, + ), + child: child, + ), ), ), - child: LimitedBox( - maxHeight: maxHeight, - child: Padding( - padding: EdgeInsets.only( - left: horizontalPadding, - right: horizontalPadding, - top: 10, - bottom: 0, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension<StackColors>()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + Center( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - width: 60, - height: 4, ), + width: 60, + height: 4, ), - const SizedBox( - height: 36, - ), - Text( - "Select emoji", - style: STextStyles.pageTitleH2(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 16, - ), - Flexible( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: GridView.builder( - itemCount: itemCount, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: emojisPerRow, - ), - itemBuilder: (context, index) { - final emoji = Emoji.all()[index]; - return GestureDetector( - onTap: () { - Navigator.of(context).pop(emoji); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: Colors.transparent, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(emoji.char), - ), - ), - ); - }, - ), - ) - ], - ), - ), - const SizedBox( - height: 24, - ), - ], + ), + if (!isDesktop) + const SizedBox( + height: 36, + ), + Text( + "Select emoji", + style: isDesktop + ? STextStyles.desktopH3(context) + : STextStyles.pageTitleH2(context), + textAlign: TextAlign.left, ), - ), + SizedBox( + height: isDesktop ? 28 : 16, + ), + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: GridView.builder( + itemCount: itemCount, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: emojisPerRow, + ), + itemBuilder: (context, index) { + final emoji = Emoji.all()[index]; + return GestureDetector( + onTap: () { + Navigator.of(context).pop(emoji); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: Colors.transparent, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + emoji.char, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : null, + ), + ), + ), + ); + }, + ), + ) + ], + ), + ), + SizedBox( + height: isDesktop ? 20 : 24, + ), + if (isDesktop) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SecondaryButton( + label: "Cancel", + width: 248, + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ], + ), + ], ), ); } diff --git a/lib/widgets/expandable.dart b/lib/widgets/expandable.dart index 47726d6d6..737f4ce7d 100644 --- a/lib/widgets/expandable.dart +++ b/lib/widgets/expandable.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; enum ExpandableState { - expanded, collapsed, + expanded, } class ExpandableController { @@ -45,11 +45,11 @@ class _ExpandableState extends State<Expandable> with TickerProviderStateMixin { Future<void> toggle() async { if (animation.isDismissed) { await animationController.forward(); - _toggleState = ExpandableState.collapsed; + _toggleState = ExpandableState.expanded; widget.onExpandChanged?.call(_toggleState); } else if (animation.isCompleted) { await animationController.reverse(); - _toggleState = ExpandableState.expanded; + _toggleState = ExpandableState.collapsed; widget.onExpandChanged?.call(_toggleState); } controller?.state = _toggleState; diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index 1da7e9012..c3fb36c70 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -46,7 +46,7 @@ class NodeCard extends ConsumerStatefulWidget { class _NodeCardState extends ConsumerState<NodeCard> { String _status = "Disconnected"; late final String nodeId; - bool _advancedIsExpanded = true; + bool _advancedIsExpanded = false; Future<void> _notifyWalletsOfUpdatedNode(WidgetRef ref) async { final managers = ref @@ -367,8 +367,8 @@ class _NodeCardState extends ConsumerState<NodeCard> { if (isDesktop) SvgPicture.asset( _advancedIsExpanded - ? Assets.svg.chevronDown - : Assets.svg.chevronUp, + ? Assets.svg.chevronUp + : Assets.svg.chevronDown, width: 12, height: 6, color: Theme.of(context) diff --git a/lib/widgets/textfields/exchange_textfield.dart b/lib/widgets/textfields/exchange_textfield.dart new file mode 100644 index 000000000..8d3c5d699 --- /dev/null +++ b/lib/widgets/textfields/exchange_textfield.dart @@ -0,0 +1,384 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.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/widgets/loading_indicator.dart'; + +class ExchangeTextField extends StatefulWidget { + const ExchangeTextField({ + Key? key, + this.borderRadius = 0, + this.background, + required this.controller, + this.buttonColor, + required this.focusNode, + this.buttonContent, + required this.textStyle, + this.onButtonTap, + this.onChanged, + this.onSubmitted, + this.onTap, + required this.isWalletCoin, + this.image, + this.ticker, + this.readOnly = false, + }) : super(key: key); + + final double borderRadius; + final Color? background; + final Color? buttonColor; + final Widget? buttonContent; + final TextEditingController controller; + final FocusNode focusNode; + final TextStyle textStyle; + final VoidCallback? onTap; + final VoidCallback? onButtonTap; + final void Function(String)? onChanged; + final void Function(String)? onSubmitted; + + final bool isWalletCoin; + final bool readOnly; + final String? image; + final String? ticker; + + @override + State<ExchangeTextField> createState() => _ExchangeTextFieldState(); +} + +class _ExchangeTextFieldState extends State<ExchangeTextField> { + late final TextEditingController controller; + late final FocusNode focusNode; + late final TextStyle textStyle; + + late final double borderRadius; + + late final Color? background; + late final Color? buttonColor; + late final Widget? buttonContent; + late final VoidCallback? onButtonTap; + late final VoidCallback? onTap; + late final void Function(String)? onChanged; + late final void Function(String)? onSubmitted; + + @override + void initState() { + borderRadius = widget.borderRadius; + background = widget.background; + buttonColor = widget.buttonColor; + controller = widget.controller; + focusNode = widget.focusNode; + buttonContent = widget.buttonContent; + textStyle = widget.textStyle; + onButtonTap = widget.onButtonTap; + onChanged = widget.onChanged; + onSubmitted = widget.onSubmitted; + onTap = widget.onTap; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: background, + borderRadius: BorderRadius.circular(borderRadius), + ), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TextField( + style: textStyle, + controller: controller, + focusNode: focusNode, + onChanged: onChanged, + onTap: onTap, + enableSuggestions: false, + autocorrect: false, + readOnly: widget.readOnly, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + left: 12, + ), + hintText: "0", + hintStyle: STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + ), + inputFormatters: [ + // regex to validate a crypto amount with 8 decimal places + TextInputFormatter.withFunction((oldValue, newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + ), + ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => onButtonTap?.call(), + child: Container( + decoration: BoxDecoration( + color: buttonColor, + borderRadius: BorderRadius.horizontal( + right: Radius.circular( + borderRadius, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Row( + children: [ + Container( + width: 18, + height: 18, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + ), + child: Builder( + builder: (context) { + final image = widget.image; + + if (image != null && image.isNotEmpty) { + return Center( + child: SvgPicture.network( + image, + height: 18, + placeholderBuilder: (_) => Container( + width: 18, + height: 18, + decoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + 18, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + 18, + ), + child: const LoadingIndicator(), + ), + ), + ), + ); + } else { + return Container( + width: 18, + height: 18, + decoration: BoxDecoration( + // color: Theme.of(context).extension<StackColors>()!.accentColorDark + borderRadius: BorderRadius.circular(18), + ), + child: SvgPicture.asset( + Assets.svg.circleQuestion, + width: 18, + height: 18, + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + ), + ); + } + }, + ), + ), + const SizedBox( + width: 6, + ), + Text( + widget.ticker ?? "-", + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ), + if (!widget.isWalletCoin) + const SizedBox( + width: 6, + ), + if (!widget.isWalletCoin) + SvgPicture.asset( + Assets.svg.chevronDown, + width: 5, + height: 2.5, + color: Theme.of(context) + .extension<StackColors>()! + .textDark, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +// experimental UNUSED +// class ExchangeTextField extends StatefulWidget { +// const ExchangeTextField({ +// Key? key, +// this.borderRadius = 0, +// this.background, +// required this.controller, +// this.buttonColor, +// required this.focusNode, +// this.buttonContent, +// required this.textStyle, +// this.onButtonTap, +// this.onChanged, +// this.onSubmitted, +// }) : super(key: key); +// +// final double borderRadius; +// final Color? background; +// final Color? buttonColor; +// final Widget? buttonContent; +// final TextEditingController controller; +// final FocusNode focusNode; +// final TextStyle textStyle; +// final VoidCallback? onButtonTap; +// final void Function(String)? onChanged; +// final void Function(String)? onSubmitted; +// +// @override +// State<ExchangeTextField> createState() => _ExchangeTextFieldState(); +// } +// +// class _ExchangeTextFieldState extends State<ExchangeTextField> { +// late final TextEditingController controller; +// late final FocusNode focusNode; +// late final TextStyle textStyle; +// +// late final double borderRadius; +// +// late final Color? background; +// late final Color? buttonColor; +// late final Widget? buttonContent; +// late final VoidCallback? onButtonTap; +// late final void Function(String)? onChanged; +// late final void Function(String)? onSubmitted; +// +// @override +// void initState() { +// borderRadius = widget.borderRadius; +// background = widget.background; +// buttonColor = widget.buttonColor; +// controller = widget.controller; +// focusNode = widget.focusNode; +// buttonContent = widget.buttonContent; +// textStyle = widget.textStyle; +// onButtonTap = widget.onButtonTap; +// onChanged = widget.onChanged; +// onSubmitted = widget.onSubmitted; +// +// super.initState(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Container( +// decoration: BoxDecoration( +// color: background, +// borderRadius: BorderRadius.circular(borderRadius), +// ), +// child: IntrinsicHeight( +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// Expanded( +// child: MouseRegion( +// cursor: SystemMouseCursors.text, +// child: GestureDetector( +// onTap: () { +// // +// }, +// child: Padding( +// padding: const EdgeInsets.only( +// left: 16, +// top: 18, +// bottom: 17, +// ), +// child: IgnorePointer( +// ignoring: true, +// child: EditableText( +// controller: controller, +// focusNode: focusNode, +// style: textStyle, +// onChanged: onChanged, +// onSubmitted: onSubmitted, +// onEditingComplete: () => print("lol"), +// autocorrect: false, +// enableSuggestions: false, +// keyboardType: const TextInputType.numberWithOptions( +// signed: false, +// decimal: true, +// ), +// inputFormatters: [ +// // regex to validate a crypto amount with 8 decimal places +// TextInputFormatter.withFunction((oldValue, +// newValue) => +// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') +// .hasMatch(newValue.text) +// ? newValue +// : oldValue), +// ], +// cursorColor: textStyle.color ?? +// Theme.of(context).backgroundColor, +// backgroundCursorColor: background ?? Colors.transparent, +// ), +// ), +// ), +// ), +// ), +// ), +// MouseRegion( +// cursor: SystemMouseCursors.click, +// child: GestureDetector( +// onTap: () => onButtonTap?.call(), +// child: Container( +// decoration: BoxDecoration( +// color: buttonColor, +// borderRadius: BorderRadius.horizontal( +// right: Radius.circular( +// borderRadius, +// ), +// ), +// ), +// child: Padding( +// padding: const EdgeInsets.symmetric( +// horizontal: 16, +// ), +// child: buttonContent, +// ), +// ), +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/widgets/trade_card.dart b/lib/widgets/trade_card.dart index 0ac8e9346..5a14a0777 100644 --- a/lib/widgets/trade_card.dart +++ b/lib/widgets/trade_card.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class TradeCard extends ConsumerWidget { @@ -49,68 +50,85 @@ class TradeCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return GestureDetector( - onTap: onTap, - child: RoundedWhiteContainer( - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(32), - ), - child: Center( - child: SvgPicture.asset( - _fetchIconAssetForStatus( - trade.status, - context, + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: onTap, + child: RoundedWhiteContainer( + padding: + isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + ), + child: Center( + child: SvgPicture.asset( + _fetchIconAssetForStatus( + trade.status, + context, + ), + width: 32, + height: 32, ), - width: 32, - height: 32, ), ), - ), - const SizedBox( - width: 12, - ), - Expanded( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ), - Text( - "${Util.isDesktop ? "-" : ""}${Decimal.tryParse(trade.payInAmount) ?? "..."} ${trade.payInCurrency.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - const SizedBox( - height: 2, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - trade.exchangeName, - style: STextStyles.label(context), - ), - Text( - Format.extractDateFrom( - trade.timestamp.millisecondsSinceEpoch ~/ 1000), - style: STextStyles.label(context), - ), - ], - ), - ], + const SizedBox( + width: 12, ), - ) - ], + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ), + Text( + "${isDesktop ? "-" : ""}${Decimal.tryParse(trade.payInAmount) ?? "..."} ${trade.payInCurrency.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + const SizedBox( + height: 2, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!isDesktop) + Text( + trade.exchangeName, + style: STextStyles.label(context), + ), + Text( + Format.extractDateFrom( + trade.timestamp.millisecondsSinceEpoch ~/ 1000), + style: STextStyles.label(context), + ), + if (isDesktop) + Text( + trade.exchangeName, + style: STextStyles.label(context), + ), + ], + ), + ], + ), + ) + ], + ), ), ), ); diff --git a/linux/my_application.cc b/linux/my_application.cc index 280895e03..9cb3acebd 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -47,7 +47,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "Stack Wallet"); } - gtk_window_set_default_size(window, 720, 1280); + gtk_window_set_default_size(window, 1220, 900); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f19..96d3fee1a 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" + "info": { + "version": 1, + "author": "xcode" }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ] +} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..55efad011 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..c8ba3b1a2 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..e85f01418 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bfb090fc6 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..877b87dc3 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..f24fbe65d 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..b81789038 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/pubspec.lock b/pubspec.lock index 0b8f49c2c..f12b25487 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -205,6 +205,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" clock: dependency: transitive description: @@ -494,7 +501,7 @@ packages: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.9.3" + version: "0.11.0" flutter_libepiccash: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bba5f6ed2..b1312427d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -141,7 +141,7 @@ dev_dependencies: integration_test: sdk: flutter build_runner: ^2.1.7 - flutter_launcher_icons: ^0.9.3 + flutter_launcher_icons: ^0.11.0 hive_generator: ^1.1.2 dependency_validator: ^3.1.2 hive_test: ^1.0.1 @@ -160,6 +160,13 @@ flutter_icons: image_path_android: assets/icon/app_icon_alpha.png image_path_ios: assets/icon/icon.png remove_alpha_ios: true + windows: + generate: true + image_path: assets/icon/icon.png + icon_size: 48 # min:48, max:256, default: 48 + macos: + generate: true + image_path: assets/icon/icon.png flutter_native_splash: image: assets/images/splash.png @@ -202,12 +209,11 @@ flutter: - assets/images/epic-cash.png - assets/images/bitcoincash.png - assets/images/namecoin.png + - assets/images/glasses.png + - assets/images/glasses-hidden.png - assets/svg/plus.svg - assets/svg/gear.svg - assets/svg/bell.svg - - assets/svg/light/bell-new.svg - - assets/svg/dark/bell-new.svg - - assets/svg/stack-icon1.svg - assets/svg/arrow-left-fa.svg - assets/svg/copy-fa.svg - assets/svg/star.svg @@ -220,10 +226,7 @@ flutter: - assets/svg/bars.svg - assets/svg/filter.svg - assets/svg/pending.svg - - assets/svg/dark/exchange-2.svg - - assets/svg/light/exchange-2.svg - assets/svg/signal-stream.svg - - assets/svg/buy-coins-icon.svg - assets/svg/Ellipse-43.svg - assets/svg/Ellipse-42.svg - assets/svg/arrow-rotate.svg @@ -234,6 +237,7 @@ flutter: - assets/svg/chevron-down.svg - assets/svg/chevron-up.svg - assets/svg/lock-keyhole.svg + - assets/svg/lock-open.svg - assets/svg/rotate-exclamation.svg - assets/svg/folder-down.svg - assets/svg/network-wired.svg @@ -265,25 +269,7 @@ flutter: - assets/svg/ellipsis-vertical1.svg - assets/svg/dice-alt.svg - assets/svg/circle-arrow-up-right2.svg - - assets/svg/dark/tx-exchange-icon.svg - - assets/svg/light/tx-exchange-icon.svg - - assets/svg/dark/tx-exchange-icon-pending.svg - - assets/svg/light/tx-exchange-icon-pending.svg - - assets/svg/dark/tx-exchange-icon-failed.svg - - assets/svg/light/tx-exchange-icon-failed.svg - assets/svg/loader.svg - - assets/svg/dark/tx-icon-send.svg - - assets/svg/light/tx-icon-send.svg - - assets/svg/dark/tx-icon-send-pending.svg - - assets/svg/light/tx-icon-send-pending.svg - - assets/svg/dark/tx-icon-send-failed.svg - - assets/svg/light/tx-icon-send-failed.svg - - assets/svg/dark/tx-icon-receive.svg - - assets/svg/light/tx-icon-receive.svg - - assets/svg/dark/tx-icon-receive-pending.svg - - assets/svg/light/tx-icon-receive-pending.svg - - assets/svg/dark/tx-icon-receive-failed.svg - - assets/svg/light/tx-icon-receive-failed.svg - assets/svg/add-backup.svg - assets/svg/auto-backup.svg - assets/svg/restore-backup.svg @@ -305,8 +291,6 @@ flutter: - assets/svg/rotate-circle.svg - assets/svg/sun-circle.svg - assets/svg/node-circle.svg - - assets/svg/dark/dark-theme.svg - - assets/svg/light/light-mode.svg - assets/svg/address-book-desktop.svg - assets/svg/about-desktop.svg - assets/svg/exchange-desktop.svg @@ -347,6 +331,56 @@ flutter: - assets/svg/exchange_icons/change_now_logo_1.svg - assets/svg/exchange_icons/simpleswap-icon.svg + # theme selectors + - assets/svg/dark-theme.svg + - assets/svg/light-mode.svg + - assets/svg/ocean-breeze-theme.svg + + # light theme specific + - assets/svg/light/tx-exchange-icon.svg + - assets/svg/light/tx-exchange-icon-pending.svg + - assets/svg/light/tx-exchange-icon-failed.svg + - assets/svg/light/tx-icon-send.svg + - assets/svg/light/tx-icon-send-pending.svg + - assets/svg/light/tx-icon-send-failed.svg + - assets/svg/light/tx-icon-receive.svg + - assets/svg/light/tx-icon-receive-pending.svg + - assets/svg/light/tx-icon-receive-failed.svg + - assets/svg/light/exchange-2.svg + - assets/svg/light/bell-new.svg + - assets/svg/light/stack-icon1.svg + - assets/svg/light/buy-coins-icon.svg + + # dark theme specific + - assets/svg/dark/tx-exchange-icon.svg + - assets/svg/dark/tx-exchange-icon-pending.svg + - assets/svg/dark/tx-exchange-icon-failed.svg + - assets/svg/dark/tx-icon-send.svg + - assets/svg/dark/tx-icon-send-pending.svg + - assets/svg/dark/tx-icon-send-failed.svg + - assets/svg/dark/tx-icon-receive.svg + - assets/svg/dark/tx-icon-receive-pending.svg + - assets/svg/dark/tx-icon-receive-failed.svg + - assets/svg/dark/exchange-2.svg + - assets/svg/dark/bell-new.svg + - assets/svg/dark/stack-icon1.svg + - assets/svg/dark/buy-coins-icon.svg + + # light theme specific + - assets/svg/oceanBreeze/tx-exchange-icon.svg + - assets/svg/oceanBreeze/tx-exchange-icon-pending.svg + - assets/svg/oceanBreeze/tx-exchange-icon-failed.svg + - assets/svg/oceanBreeze/tx-icon-send.svg + - assets/svg/oceanBreeze/tx-icon-send-pending.svg + - assets/svg/oceanBreeze/tx-icon-send-failed.svg + - assets/svg/oceanBreeze/tx-icon-receive.svg + - assets/svg/oceanBreeze/tx-icon-receive-pending.svg + - assets/svg/oceanBreeze/tx-icon-receive-failed.svg + - assets/svg/oceanBreeze/exchange-2.svg + - assets/svg/oceanBreeze/bell-new.svg + - assets/svg/oceanBreeze/stack-icon1.svg + - assets/svg/oceanBreeze/buy-coins-icon.svg + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see diff --git a/test/widget_tests/address_book_card_test.dart b/test/widget_tests/address_book_card_test.dart index 7c53d8d50..07b1387df 100644 --- a/test/widget_tests/address_book_card_test.dart +++ b/test/widget_tests/address_book_card_test.dart @@ -70,7 +70,7 @@ void main() { await widgetTester.tap(find.byType(RawMaterialButton)); expect(find.byType(ContactPopUp), findsOneWidget); } else if (Util.isDesktop) { - expect(find.byType(RawMaterialButton), findsOneWidget); + expect(find.byType(RawMaterialButton), findsNothing); } }); } diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20caf..eee73d91b 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ