diff --git a/.fvmrc b/.fvmrc index 3135e2b..3ca65ff 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.32.2" + "flutter": "3.32.8" } \ No newline at end of file diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index de75e55..fd7d184 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -30,7 +30,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: 3.32.2 + flutter-version: 3.32.8 - name: Install dependencies run: flutter pub get && dart run build_runner build --delete-conflicting-outputs && flutter gen-l10n && dart run routefly && dart run web3kit:generate_abis - name: Run tests diff --git a/.vscode/settings.json b/.vscode/settings.json index de056e2..4903277 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.32.2", + "dart.flutterSdkPath": ".fvm/versions/3.32.8", "dart.lineLength": 120 } \ No newline at end of file diff --git a/assets/lotties/numbers.json b/assets/lotties/numbers.json new file mode 100644 index 0000000..7cf2693 --- /dev/null +++ b/assets/lotties/numbers.json @@ -0,0 +1,4985 @@ +{ + "v": "5.5.2", + "fr": 29.9700012207031, + "ip": 0, + "op": 26.0000010590017, + "w": 400, + "h": 400, + "nm": "Comp 1", + "ddd": 0, + "assets": [], + "fonts": { + "list": [ + { + "fName": "NotoSans-Bold", + "fFamily": "Noto Sans", + "fStyle": "Bold", + "ascent": 75.9994506835938 + } + ] + }, + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 2", + "td": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 658, + 984, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 130, + 130, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 240, + 240 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 1, + 1, + 1, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 2, + "ix": 5 + }, + "lc": 1, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.4, + 0.4, + 1, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -352.65, + -603.797 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 120, + 120 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 50, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 900.000036657751, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 5, + "nm": "73 44 22 31 43 04 09 56 74 88 93 34 88 92 28 43 03 08 87 63 21 64 88 ", + "tt": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 0, + "s": [ + 266, + 336, + 0 + ], + "to": [ + -3, + -856, + 0 + ], + "ti": [ + 3, + 856, + 0 + ] + }, + { + "t": 26.0000010590017, + "s": [ + 248, + -4800, + 0 + ] + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "t": { + "d": { + "k": [ + { + "s": { + "sz": [ + 216.727233886719, + 5028 + ], + "ps": [ + -152, + -148 + ], + "s": 130, + "f": "NotoSans-Bold", + "t": "73\r44\r22\r31\r43\r04\r09\r56\r74\r88\r93\r34\r88\r92\r28\r43\r03\r08\r87\r63\r21\r64\r88\r", + "j": 2, + "tr": 0, + "lh": 200, + "ls": 0, + "fc": [ + 0, + 0, + 1, + 1 + ] + }, + "t": 0 + } + ] + }, + "p": {}, + "m": { + "g": 1, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + } + }, + "a": [] + }, + "ip": 0, + "op": 900.000036657751, + "st": 0, + "bm": 0 + } + ], + "markers": [], + "chars": [ + { + "ch": "7", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 25.879, + 0 + ], + [ + 51.904, + -61.816 + ], + [ + 51.904, + -71.289 + ], + [ + 2.686, + -71.289 + ], + [ + 2.686, + -58.594 + ], + [ + 35.498, + -58.594 + ], + [ + 10.107, + 0 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "7", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "7", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "3", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.123, + 2.1 + ], + [ + 2.051, + 1.449 + ], + [ + 2.881, + 0.765 + ], + [ + 3.516, + 0 + ], + [ + 2.18, + -0.358 + ], + [ + 1.888, + -0.635 + ], + [ + 1.611, + -0.846 + ], + [ + 1.367, + -1.009 + ], + [ + 0, + 0 + ], + [ + -0.961, + 0.57 + ], + [ + -1.156, + 0.472 + ], + [ + -1.351, + 0.31 + ], + [ + -1.562, + 0 + ], + [ + -1.579, + -1.432 + ], + [ + 0, + -2.539 + ], + [ + 0.488, + -1.237 + ], + [ + 1.188, + -0.928 + ], + [ + 1.985, + -0.537 + ], + [ + 2.995, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -2.181, + -0.455 + ], + [ + -1.351, + -0.83 + ], + [ + -0.603, + -1.188 + ], + [ + 0, + -1.465 + ], + [ + 0.471, + -1.318 + ], + [ + 1.106, + -0.944 + ], + [ + 1.855, + -0.52 + ], + [ + 2.799, + 0 + ], + [ + 1.579, + 0.195 + ], + [ + 1.627, + 0.407 + ], + [ + 1.627, + 0.586 + ], + [ + 1.465, + 0.781 + ], + [ + 0, + 0 + ], + [ + -3.386, + -0.635 + ], + [ + -3.841, + 0 + ], + [ + -3.532, + 1.09 + ], + [ + -2.328, + 1.953 + ], + [ + -1.123, + 2.654 + ], + [ + 0, + 3.125 + ], + [ + 2.962, + 2.783 + ], + [ + 5.762, + 0.716 + ], + [ + 0, + 0 + ], + [ + -1.872, + 0.993 + ], + [ + -1.384, + 1.482 + ], + [ + -0.798, + 1.97 + ], + [ + 0, + 2.474 + ] + ], + "o": [ + [ + -1.123, + -2.1 + ], + [ + -2.051, + -1.448 + ], + [ + -2.881, + -0.764 + ], + [ + -2.474, + 0 + ], + [ + -2.181, + 0.359 + ], + [ + -1.888, + 0.635 + ], + [ + -1.611, + 0.847 + ], + [ + 0, + 0 + ], + [ + 0.781, + -0.553 + ], + [ + 0.96, + -0.569 + ], + [ + 1.155, + -0.471 + ], + [ + 1.35, + -0.309 + ], + [ + 3.418, + 0 + ], + [ + 1.579, + 1.433 + ], + [ + 0, + 1.4 + ], + [ + -0.488, + 1.237 + ], + [ + -1.189, + 0.928 + ], + [ + -1.986, + 0.537 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 3.19, + 0 + ], + [ + 2.18, + 0.456 + ], + [ + 1.35, + 0.83 + ], + [ + 0.602, + 1.189 + ], + [ + 0, + 1.562 + ], + [ + -0.472, + 1.318 + ], + [ + -1.107, + 0.945 + ], + [ + -1.855, + 0.521 + ], + [ + -1.4, + 0 + ], + [ + -1.579, + -0.195 + ], + [ + -1.628, + -0.407 + ], + [ + -1.628, + -0.586 + ], + [ + 0, + 0 + ], + [ + 2.93, + 1.302 + ], + [ + 3.385, + 0.635 + ], + [ + 4.785, + 0 + ], + [ + 3.532, + -1.09 + ], + [ + 2.327, + -1.953 + ], + [ + 1.123, + -2.653 + ], + [ + 0, + -4.72 + ], + [ + -2.962, + -2.783 + ], + [ + 0, + 0 + ], + [ + 2.18, + -0.52 + ], + [ + 1.871, + -0.993 + ], + [ + 1.383, + -1.481 + ], + [ + 0.797, + -1.969 + ], + [ + 0, + -2.702 + ] + ], + "v": [ + [ + 47.437, + -62.622 + ], + [ + 42.676, + -67.944 + ], + [ + 35.278, + -71.265 + ], + [ + 25.684, + -72.412 + ], + [ + 18.701, + -71.875 + ], + [ + 12.598, + -70.386 + ], + [ + 7.349, + -68.164 + ], + [ + 2.881, + -65.381 + ], + [ + 10.498, + -55.322 + ], + [ + 13.11, + -57.007 + ], + [ + 16.284, + -58.569 + ], + [ + 20.044, + -59.741 + ], + [ + 24.414, + -60.205 + ], + [ + 31.909, + -58.057 + ], + [ + 34.277, + -52.1 + ], + [ + 33.545, + -48.145 + ], + [ + 31.03, + -44.897 + ], + [ + 26.27, + -42.7 + ], + [ + 18.799, + -41.895 + ], + [ + 13.818, + -41.895 + ], + [ + 13.818, + -31.299 + ], + [ + 18.896, + -31.299 + ], + [ + 26.953, + -30.615 + ], + [ + 32.251, + -28.687 + ], + [ + 35.181, + -25.659 + ], + [ + 36.084, + -21.68 + ], + [ + 35.376, + -17.358 + ], + [ + 33.008, + -13.965 + ], + [ + 28.564, + -11.768 + ], + [ + 21.582, + -10.986 + ], + [ + 17.114, + -11.279 + ], + [ + 12.305, + -12.183 + ], + [ + 7.422, + -13.672 + ], + [ + 2.783, + -15.723 + ], + [ + 2.783, + -2.881 + ], + [ + 12.256, + 0.024 + ], + [ + 23.096, + 0.977 + ], + [ + 35.571, + -0.659 + ], + [ + 44.36, + -5.225 + ], + [ + 49.536, + -12.134 + ], + [ + 51.221, + -20.801 + ], + [ + 46.777, + -32.056 + ], + [ + 33.691, + -37.305 + ], + [ + 33.691, + -37.598 + ], + [ + 39.771, + -39.868 + ], + [ + 44.653, + -43.579 + ], + [ + 47.925, + -48.755 + ], + [ + 49.121, + -55.42 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "3", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "3", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "\r", + "size": 130, + "style": "Bold", + "w": 0, + "fFamily": "Noto Sans" + }, + { + "ch": "4", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 52.979, + -26.514 + ], + [ + 44.385, + -26.514 + ], + [ + 44.385, + -71.387 + ], + [ + 30.518, + -71.387 + ], + [ + 0.195, + -25.293 + ], + [ + 0.195, + -14.795 + ], + [ + 29.688, + -14.795 + ], + [ + 29.688, + 0 + ], + [ + 44.385, + 0 + ], + [ + 44.385, + -14.795 + ], + [ + 52.979, + -14.795 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "4", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.732, + 1.53 + ], + [ + -0.586, + 1.367 + ], + [ + 0, + 0 + ], + [ + 0.049, + -0.732 + ], + [ + 0.049, + -0.977 + ], + [ + 0.049, + -1.074 + ], + [ + 0.032, + -1.009 + ], + [ + 0.016, + -0.797 + ], + [ + 0, + -0.423 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.944, + -1.53 + ], + [ + 0.732, + -1.53 + ], + [ + 0, + 0 + ], + [ + -0.033, + 0.326 + ], + [ + -0.049, + 0.732 + ], + [ + -0.049, + 0.977 + ], + [ + -0.049, + 1.074 + ], + [ + -0.033, + 1.009 + ], + [ + -0.017, + 0.798 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 13.477, + -26.514 + ], + [ + 25.391, + -44.58 + ], + [ + 27.905, + -49.17 + ], + [ + 29.883, + -53.516 + ], + [ + 30.322, + -53.516 + ], + [ + 30.2, + -51.929 + ], + [ + 30.054, + -49.365 + ], + [ + 29.907, + -46.289 + ], + [ + 29.785, + -43.164 + ], + [ + 29.712, + -40.454 + ], + [ + 29.688, + -38.623 + ], + [ + 29.688, + -26.514 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "4", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "4", + "np": 5, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "2", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -2.23, + 2.425 + ], + [ + -1.693, + 2.458 + ], + [ + -0.977, + 2.556 + ], + [ + 0, + 2.8 + ], + [ + 1.025, + 2.474 + ], + [ + 1.92, + 1.742 + ], + [ + 2.718, + 0.945 + ], + [ + 3.418, + 0 + ], + [ + 2.311, + -0.569 + ], + [ + 1.92, + -0.944 + ], + [ + 1.595, + -1.221 + ], + [ + 1.465, + -1.334 + ], + [ + 0, + 0 + ], + [ + -2.377, + 1.27 + ], + [ + -2.572, + 0 + ], + [ + -1.449, + -1.334 + ], + [ + 0, + -2.409 + ], + [ + 0.684, + -1.888 + ], + [ + 1.237, + -1.823 + ], + [ + 1.725, + -1.92 + ], + [ + 2.116, + -2.278 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2.539, + -2.506 + ], + [ + 2.229, + -2.425 + ], + [ + 1.692, + -2.457 + ], + [ + 0.977, + -2.555 + ], + [ + 0, + -3.092 + ], + [ + -1.025, + -2.473 + ], + [ + -1.921, + -1.741 + ], + [ + -2.718, + -0.944 + ], + [ + -2.865, + 0 + ], + [ + -2.312, + 0.57 + ], + [ + -1.921, + 0.945 + ], + [ + -1.595, + 1.221 + ], + [ + 0, + 0 + ], + [ + 2.604, + -2.344 + ], + [ + 2.376, + -1.27 + ], + [ + 2.441, + 0 + ], + [ + 1.448, + 1.335 + ], + [ + 0, + 2.181 + ], + [ + -0.684, + 1.888 + ], + [ + -1.237, + 1.823 + ], + [ + -1.726, + 1.921 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 51.904, + -12.695 + ], + [ + 22.803, + -12.695 + ], + [ + 22.803, + -13.379 + ], + [ + 31.396, + -22.021 + ], + [ + 38.55, + -29.419 + ], + [ + 44.434, + -36.743 + ], + [ + 48.438, + -44.263 + ], + [ + 49.902, + -52.295 + ], + [ + 48.364, + -60.645 + ], + [ + 43.945, + -66.968 + ], + [ + 36.987, + -70.996 + ], + [ + 27.783, + -72.412 + ], + [ + 20.02, + -71.558 + ], + [ + 13.672, + -69.287 + ], + [ + 8.398, + -66.04 + ], + [ + 3.809, + -62.207 + ], + [ + 12.012, + -52.49 + ], + [ + 19.482, + -57.91 + ], + [ + 26.904, + -59.814 + ], + [ + 32.739, + -57.812 + ], + [ + 34.912, + -52.197 + ], + [ + 33.887, + -46.094 + ], + [ + 31.006, + -40.527 + ], + [ + 26.562, + -34.912 + ], + [ + 20.801, + -28.613 + ], + [ + 4.004, + -10.498 + ], + [ + 4.004, + 0 + ], + [ + 51.904, + 0 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "2", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "2", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "1", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.521, + 0.488 + ], + [ + -0.488, + 0.472 + ], + [ + -0.391, + 0.391 + ], + [ + -0.163, + 0.195 + ], + [ + 0.049, + -1.302 + ], + [ + 0.049, + -1.286 + ], + [ + 0.016, + -1.188 + ], + [ + 0, + -0.846 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.488, + -0.391 + ], + [ + 0.52, + -0.488 + ], + [ + 0.488, + -0.471 + ], + [ + 0.391, + -0.391 + ], + [ + -0.033, + 1.074 + ], + [ + -0.049, + 1.302 + ], + [ + -0.049, + 1.286 + ], + [ + -0.017, + 1.189 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 39.893, + -71.387 + ], + [ + 27.49, + -71.387 + ], + [ + 4.492, + -53.076 + ], + [ + 11.816, + -43.994 + ], + [ + 20.02, + -50.586 + ], + [ + 21.533, + -51.904 + ], + [ + 23.047, + -53.345 + ], + [ + 24.365, + -54.639 + ], + [ + 25.195, + -55.518 + ], + [ + 25.073, + -51.953 + ], + [ + 24.927, + -48.071 + ], + [ + 24.829, + -44.36 + ], + [ + 24.805, + -41.309 + ], + [ + 24.805, + 0 + ], + [ + 39.893, + 0 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "1", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "0", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.009, + 4.558 + ], + [ + 2.051, + 3.158 + ], + [ + 3.059, + 1.66 + ], + [ + 4.102, + 0 + ], + [ + 3.092, + -1.676 + ], + [ + 1.953, + -3.157 + ], + [ + 0.895, + -4.557 + ], + [ + 0, + -5.762 + ], + [ + -0.993, + -4.524 + ], + [ + -2.019, + -3.157 + ], + [ + -3.06, + -1.66 + ], + [ + -4.134, + 0 + ], + [ + -3.109, + 1.66 + ], + [ + -1.97, + 3.158 + ], + [ + -0.896, + 4.525 + ], + [ + 0, + 5.762 + ] + ], + "o": [ + [ + -1.009, + -4.557 + ], + [ + -2.051, + -3.157 + ], + [ + -3.06, + -1.66 + ], + [ + -4.395, + 0 + ], + [ + -3.093, + 1.677 + ], + [ + -1.953, + 3.158 + ], + [ + -0.896, + 4.558 + ], + [ + 0, + 5.762 + ], + [ + 0.993, + 4.525 + ], + [ + 2.018, + 3.158 + ], + [ + 3.059, + 1.66 + ], + [ + 4.395, + 0 + ], + [ + 3.108, + -1.66 + ], + [ + 1.969, + -3.157 + ], + [ + 0.895, + -4.524 + ], + [ + 0, + -5.794 + ] + ], + "v": [ + [ + 50.488, + -51.221 + ], + [ + 45.898, + -62.793 + ], + [ + 38.232, + -70.02 + ], + [ + 27.49, + -72.51 + ], + [ + 16.26, + -69.995 + ], + [ + 8.691, + -62.744 + ], + [ + 4.419, + -51.172 + ], + [ + 3.076, + -35.693 + ], + [ + 4.565, + -20.264 + ], + [ + 9.082, + -8.74 + ], + [ + 16.699, + -1.514 + ], + [ + 27.49, + 0.977 + ], + [ + 38.745, + -1.514 + ], + [ + 46.362, + -8.74 + ], + [ + 50.659, + -20.264 + ], + [ + 52.002, + -35.693 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "0", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.326, + 3.076 + ], + [ + -0.749, + 2.051 + ], + [ + -1.189, + 1.042 + ], + [ + -1.726, + 0 + ], + [ + -1.482, + -4.118 + ], + [ + 0, + -8.17 + ], + [ + 1.481, + -4.069 + ], + [ + 3.385, + 0 + ], + [ + 1.399, + 4.102 + ], + [ + 0, + 8.138 + ] + ], + "o": [ + [ + 0.325, + -3.076 + ], + [ + 0.748, + -2.051 + ], + [ + 1.188, + -1.041 + ], + [ + 3.385, + 0 + ], + [ + 1.481, + 4.118 + ], + [ + 0, + 8.203 + ], + [ + -1.482, + 4.07 + ], + [ + -3.451, + 0 + ], + [ + -1.4, + -4.102 + ], + [ + 0, + -4.069 + ] + ], + "v": [ + [ + 18.604, + -46.411 + ], + [ + 20.215, + -54.102 + ], + [ + 23.12, + -58.74 + ], + [ + 27.49, + -60.303 + ], + [ + 34.79, + -54.126 + ], + [ + 37.012, + -35.693 + ], + [ + 34.79, + -17.285 + ], + [ + 27.49, + -11.182 + ], + [ + 20.215, + -17.334 + ], + [ + 18.115, + -35.693 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "0", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "0", + "np": 5, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "9", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.302, + 3.939 + ], + [ + 2.246, + 2.572 + ], + [ + 3.059, + 1.254 + ], + [ + 3.516, + 0 + ], + [ + 2.881, + -1.139 + ], + [ + 2.018, + -2.132 + ], + [ + 1.09, + -3.059 + ], + [ + 0, + -3.873 + ], + [ + -0.912, + -2.799 + ], + [ + -1.709, + -1.92 + ], + [ + -2.474, + -1.009 + ], + [ + -3.093, + 0 + ], + [ + -1.562, + 0.423 + ], + [ + -1.237, + 0.749 + ], + [ + -0.912, + 1.009 + ], + [ + -0.652, + 1.172 + ], + [ + 0, + 0 + ], + [ + 0.7, + -2.734 + ], + [ + 1.66, + -1.969 + ], + [ + 2.832, + -1.09 + ], + [ + 4.395, + 0 + ], + [ + 1.383, + 0.179 + ], + [ + 1.204, + 0.326 + ], + [ + 0, + 0 + ], + [ + -0.781, + -0.082 + ], + [ + -0.83, + -0.049 + ], + [ + -0.814, + -0.033 + ], + [ + -0.684, + 0 + ], + [ + -3.532, + 1.123 + ], + [ + -2.523, + 1.953 + ], + [ + -1.644, + 2.686 + ], + [ + -0.961, + 3.093 + ], + [ + -0.375, + 3.402 + ], + [ + 0, + 3.418 + ] + ], + "o": [ + [ + -1.302, + -3.938 + ], + [ + -2.246, + -2.571 + ], + [ + -3.06, + -1.253 + ], + [ + -3.581, + 0 + ], + [ + -2.881, + 1.14 + ], + [ + -2.019, + 2.132 + ], + [ + -1.091, + 3.06 + ], + [ + 0, + 3.613 + ], + [ + 0.911, + 2.8 + ], + [ + 1.709, + 1.921 + ], + [ + 2.473, + 1.009 + ], + [ + 1.92, + 0 + ], + [ + 1.562, + -0.423 + ], + [ + 1.237, + -0.748 + ], + [ + 0.911, + -1.009 + ], + [ + 0, + 0 + ], + [ + -0.163, + 3.288 + ], + [ + -0.7, + 2.734 + ], + [ + -1.66, + 1.97 + ], + [ + -2.832, + 1.091 + ], + [ + -1.433, + 0 + ], + [ + -1.384, + -0.179 + ], + [ + 0, + 0 + ], + [ + 0.586, + 0.13 + ], + [ + 0.781, + 0.081 + ], + [ + 0.83, + 0.049 + ], + [ + 0.813, + 0.032 + ], + [ + 4.752, + 0 + ], + [ + 3.532, + -1.123 + ], + [ + 2.522, + -1.953 + ], + [ + 1.643, + -2.686 + ], + [ + 0.96, + -3.092 + ], + [ + 0.374, + -3.401 + ], + [ + 0, + -5.338 + ] + ], + "v": [ + [ + 49.561, + -54.834 + ], + [ + 44.238, + -64.6 + ], + [ + 36.279, + -70.337 + ], + [ + 26.416, + -72.217 + ], + [ + 16.724, + -70.508 + ], + [ + 9.375, + -65.601 + ], + [ + 4.712, + -57.812 + ], + [ + 3.076, + -47.412 + ], + [ + 4.443, + -37.793 + ], + [ + 8.374, + -30.713 + ], + [ + 14.648, + -26.318 + ], + [ + 22.998, + -24.805 + ], + [ + 28.223, + -25.439 + ], + [ + 32.422, + -27.197 + ], + [ + 35.645, + -29.834 + ], + [ + 37.988, + -33.105 + ], + [ + 38.623, + -33.105 + ], + [ + 37.329, + -24.072 + ], + [ + 33.789, + -17.017 + ], + [ + 27.051, + -12.427 + ], + [ + 16.211, + -10.791 + ], + [ + 11.987, + -11.06 + ], + [ + 8.105, + -11.816 + ], + [ + 8.105, + 0.293 + ], + [ + 10.156, + 0.61 + ], + [ + 12.573, + 0.806 + ], + [ + 15.039, + 0.928 + ], + [ + 17.285, + 0.977 + ], + [ + 29.712, + -0.708 + ], + [ + 38.794, + -5.322 + ], + [ + 45.044, + -12.28 + ], + [ + 48.95, + -20.947 + ], + [ + 50.952, + -30.688 + ], + [ + 51.514, + -40.918 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "9", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -1.221, + -0.813 + ], + [ + -0.83, + -1.318 + ], + [ + -0.407, + -1.643 + ], + [ + 0, + -1.66 + ], + [ + 0.471, + -1.172 + ], + [ + 0.879, + -0.944 + ], + [ + 1.237, + -0.586 + ], + [ + 1.497, + 0 + ], + [ + 1.465, + 2.019 + ], + [ + 0, + 3.451 + ], + [ + -0.439, + 1.579 + ], + [ + -0.798, + 1.025 + ], + [ + -1.107, + 0.488 + ], + [ + -1.335, + 0 + ] + ], + "o": [ + [ + 1.221, + 0.814 + ], + [ + 0.83, + 1.318 + ], + [ + 0.407, + 1.644 + ], + [ + 0, + 1.205 + ], + [ + -0.472, + 1.172 + ], + [ + -0.879, + 0.945 + ], + [ + -1.237, + 0.586 + ], + [ + -2.962, + 0 + ], + [ + -1.465, + -2.018 + ], + [ + 0, + -2.148 + ], + [ + 0.439, + -1.579 + ], + [ + 0.797, + -1.025 + ], + [ + 1.106, + -0.488 + ], + [ + 1.627, + 0 + ] + ], + "v": [ + [ + 30.981, + -58.887 + ], + [ + 34.058, + -55.688 + ], + [ + 35.913, + -51.245 + ], + [ + 36.523, + -46.289 + ], + [ + 35.815, + -42.725 + ], + [ + 33.789, + -39.551 + ], + [ + 30.615, + -37.256 + ], + [ + 26.514, + -36.377 + ], + [ + 19.873, + -39.404 + ], + [ + 17.676, + -47.607 + ], + [ + 18.335, + -53.198 + ], + [ + 20.19, + -57.104 + ], + [ + 23.047, + -59.375 + ], + [ + 26.709, + -60.107 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "9", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "9", + "np": 5, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "5", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.383, + -0.227 + ], + [ + 1.041, + -0.227 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.847, + 0.179 + ], + [ + -0.928, + 0.163 + ], + [ + -0.961, + 0.098 + ], + [ + -0.847, + 0 + ], + [ + -2.23, + -1.839 + ], + [ + 0, + -3.516 + ], + [ + 2.278, + -1.807 + ], + [ + 4.362, + 0 + ], + [ + 1.627, + 0.244 + ], + [ + 1.611, + 0.407 + ], + [ + 1.497, + 0.57 + ], + [ + 1.139, + 0.652 + ], + [ + 0, + 0 + ], + [ + -1.449, + -0.488 + ], + [ + -1.611, + -0.309 + ], + [ + -1.758, + -0.163 + ], + [ + -1.791, + 0 + ], + [ + -3.467, + 1.139 + ], + [ + -2.344, + 2.132 + ], + [ + -1.205, + 3.076 + ], + [ + 0, + 3.874 + ], + [ + 1.106, + 2.718 + ], + [ + 1.953, + 1.823 + ], + [ + 2.637, + 0.945 + ], + [ + 3.059, + 0 + ] + ], + "o": [ + [ + -1.384, + 0.228 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.618, + -0.195 + ], + [ + 0.846, + -0.179 + ], + [ + 0.928, + -0.163 + ], + [ + 0.96, + -0.098 + ], + [ + 4.655, + 0 + ], + [ + 2.229, + 1.839 + ], + [ + 0, + 3.939 + ], + [ + -2.279, + 1.807 + ], + [ + -1.465, + 0 + ], + [ + -1.628, + -0.244 + ], + [ + -1.611, + -0.407 + ], + [ + -1.498, + -0.569 + ], + [ + 0, + 0 + ], + [ + 1.172, + 0.652 + ], + [ + 1.448, + 0.488 + ], + [ + 1.611, + 0.309 + ], + [ + 1.758, + 0.163 + ], + [ + 4.492, + 0 + ], + [ + 3.467, + -1.139 + ], + [ + 2.344, + -2.132 + ], + [ + 1.204, + -3.076 + ], + [ + 0, + -3.516 + ], + [ + -1.107, + -2.718 + ], + [ + -1.953, + -1.823 + ], + [ + -2.637, + -0.944 + ], + [ + -2.181, + 0 + ] + ], + "v": [ + [ + 23.853, + -45.264 + ], + [ + 20.215, + -44.58 + ], + [ + 21.387, + -58.594 + ], + [ + 45.996, + -58.594 + ], + [ + 45.996, + -71.387 + ], + [ + 8.301, + -71.387 + ], + [ + 5.615, + -35.01 + ], + [ + 11.621, + -31.787 + ], + [ + 13.818, + -32.349 + ], + [ + 16.479, + -32.861 + ], + [ + 19.312, + -33.252 + ], + [ + 22.021, + -33.398 + ], + [ + 32.349, + -30.64 + ], + [ + 35.693, + -22.607 + ], + [ + 32.275, + -13.989 + ], + [ + 22.314, + -11.279 + ], + [ + 17.676, + -11.646 + ], + [ + 12.817, + -12.622 + ], + [ + 8.154, + -14.087 + ], + [ + 4.199, + -15.918 + ], + [ + 4.199, + -2.881 + ], + [ + 8.13, + -1.172 + ], + [ + 12.72, + 0.024 + ], + [ + 17.773, + 0.732 + ], + [ + 23.096, + 0.977 + ], + [ + 35.034, + -0.732 + ], + [ + 43.75, + -5.64 + ], + [ + 49.072, + -13.452 + ], + [ + 50.879, + -23.877 + ], + [ + 49.219, + -33.228 + ], + [ + 44.629, + -40.039 + ], + [ + 37.744, + -44.189 + ], + [ + 29.199, + -45.605 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "5", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "5", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "6", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -1.286, + -3.922 + ], + [ + -2.263, + -2.571 + ], + [ + -3.06, + -1.253 + ], + [ + -3.516, + 0 + ], + [ + -2.865, + 1.123 + ], + [ + -2.019, + 2.132 + ], + [ + -1.091, + 3.06 + ], + [ + 0, + 3.874 + ], + [ + 0.911, + 2.8 + ], + [ + 1.709, + 1.921 + ], + [ + 2.473, + 1.009 + ], + [ + 3.092, + 0 + ], + [ + 1.546, + -0.423 + ], + [ + 1.237, + -0.732 + ], + [ + 0.928, + -1.025 + ], + [ + 0.651, + -1.172 + ], + [ + 0, + 0 + ], + [ + -0.7, + 2.734 + ], + [ + -1.66, + 1.953 + ], + [ + -2.832, + 1.091 + ], + [ + -4.395, + 0 + ], + [ + -1.384, + -0.179 + ], + [ + -1.237, + -0.293 + ], + [ + 0, + 0 + ], + [ + 0.781, + 0.098 + ], + [ + 0.83, + 0.049 + ], + [ + 0.813, + 0.033 + ], + [ + 0.684, + 0 + ], + [ + 3.532, + -1.123 + ], + [ + 2.522, + -1.969 + ], + [ + 1.643, + -2.669 + ], + [ + 0.96, + -3.108 + ], + [ + 0.374, + -3.385 + ], + [ + 0, + -3.418 + ] + ], + "o": [ + [ + 1.286, + 3.923 + ], + [ + 2.262, + 2.572 + ], + [ + 3.059, + 1.253 + ], + [ + 3.58, + 0 + ], + [ + 2.864, + -1.123 + ], + [ + 2.018, + -2.132 + ], + [ + 1.09, + -3.059 + ], + [ + 0, + -3.613 + ], + [ + -0.912, + -2.799 + ], + [ + -1.709, + -1.92 + ], + [ + -2.474, + -1.009 + ], + [ + -1.921, + 0 + ], + [ + -1.546, + 0.423 + ], + [ + -1.237, + 0.732 + ], + [ + -0.928, + 1.025 + ], + [ + 0, + 0 + ], + [ + 0.163, + -3.32 + ], + [ + 0.7, + -2.734 + ], + [ + 1.66, + -1.953 + ], + [ + 2.832, + -1.09 + ], + [ + 1.399, + 0 + ], + [ + 1.383, + 0.179 + ], + [ + 0, + 0 + ], + [ + -0.619, + -0.13 + ], + [ + -0.781, + -0.098 + ], + [ + -0.83, + -0.049 + ], + [ + -0.814, + -0.032 + ], + [ + -4.72, + 0 + ], + [ + -3.532, + 1.123 + ], + [ + -2.523, + 1.97 + ], + [ + -1.644, + 2.67 + ], + [ + -0.961, + 3.109 + ], + [ + -0.375, + 3.386 + ], + [ + 0, + 5.371 + ] + ], + "v": [ + [ + 5.64, + -16.382 + ], + [ + 10.962, + -6.641 + ], + [ + 18.945, + -0.903 + ], + [ + 28.809, + 0.977 + ], + [ + 38.477, + -0.708 + ], + [ + 45.801, + -5.591 + ], + [ + 50.464, + -13.379 + ], + [ + 52.1, + -23.779 + ], + [ + 50.732, + -33.398 + ], + [ + 46.802, + -40.479 + ], + [ + 40.527, + -44.873 + ], + [ + 32.178, + -46.387 + ], + [ + 26.978, + -45.752 + ], + [ + 22.803, + -44.019 + ], + [ + 19.556, + -41.382 + ], + [ + 17.188, + -38.086 + ], + [ + 16.602, + -38.086 + ], + [ + 17.896, + -47.168 + ], + [ + 21.436, + -54.199 + ], + [ + 28.174, + -58.765 + ], + [ + 39.014, + -60.4 + ], + [ + 43.188, + -60.132 + ], + [ + 47.119, + -59.424 + ], + [ + 47.119, + -71.484 + ], + [ + 45.02, + -71.826 + ], + [ + 42.603, + -72.046 + ], + [ + 40.137, + -72.168 + ], + [ + 37.891, + -72.217 + ], + [ + 25.513, + -70.532 + ], + [ + 16.431, + -65.894 + ], + [ + 10.181, + -58.936 + ], + [ + 6.274, + -50.269 + ], + [ + 4.272, + -40.527 + ], + [ + 3.711, + -30.322 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "6", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.221, + 0.814 + ], + [ + 0.83, + 1.318 + ], + [ + 0.407, + 1.644 + ], + [ + 0, + 1.66 + ], + [ + -0.472, + 1.172 + ], + [ + -0.879, + 0.945 + ], + [ + -1.237, + 0.586 + ], + [ + -1.498, + 0 + ], + [ + -1.449, + -2.002 + ], + [ + 0, + -3.483 + ], + [ + 0.439, + -1.562 + ], + [ + 0.797, + -1.025 + ], + [ + 1.09, + -0.504 + ], + [ + 1.334, + 0 + ] + ], + "o": [ + [ + -1.221, + -0.813 + ], + [ + -0.83, + -1.318 + ], + [ + -0.407, + -1.643 + ], + [ + 0, + -1.204 + ], + [ + 0.471, + -1.172 + ], + [ + 0.879, + -0.944 + ], + [ + 1.237, + -0.586 + ], + [ + 2.962, + 0 + ], + [ + 1.448, + 2.002 + ], + [ + 0, + 2.148 + ], + [ + -0.439, + 1.562 + ], + [ + -0.798, + 1.025 + ], + [ + -1.091, + 0.505 + ], + [ + -1.628, + 0 + ] + ], + "v": [ + [ + 24.243, + -12.305 + ], + [ + 21.167, + -15.503 + ], + [ + 19.312, + -19.946 + ], + [ + 18.701, + -24.902 + ], + [ + 19.409, + -28.467 + ], + [ + 21.436, + -31.641 + ], + [ + 24.609, + -33.936 + ], + [ + 28.711, + -34.814 + ], + [ + 35.327, + -31.812 + ], + [ + 37.5, + -23.584 + ], + [ + 36.841, + -18.018 + ], + [ + 34.985, + -14.136 + ], + [ + 32.153, + -11.841 + ], + [ + 28.516, + -11.084 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "6", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "6", + "np": 5, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + }, + { + "ch": "8", + "size": 130, + "style": "Bold", + "w": 55.08, + "data": { + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 2.637, + -0.716 + ], + [ + 2.018, + -1.448 + ], + [ + 1.188, + -2.132 + ], + [ + 0, + -2.832 + ], + [ + -0.603, + -1.758 + ], + [ + -1.025, + -1.448 + ], + [ + -1.4, + -1.188 + ], + [ + -1.595, + -1.009 + ], + [ + 1.66, + -1.204 + ], + [ + 1.237, + -1.562 + ], + [ + 0.716, + -1.953 + ], + [ + 0, + -2.441 + ], + [ + -1.058, + -2.376 + ], + [ + -2.051, + -1.627 + ], + [ + -2.995, + -0.879 + ], + [ + -3.841, + 0 + ], + [ + -2.946, + 0.911 + ], + [ + -2.116, + 1.693 + ], + [ + -1.172, + 2.409 + ], + [ + 0, + 2.962 + ], + [ + 0.846, + 1.921 + ], + [ + 1.399, + 1.546 + ], + [ + 1.807, + 1.237 + ], + [ + 1.888, + 1.009 + ], + [ + -1.562, + 1.107 + ], + [ + -1.14, + 1.416 + ], + [ + -0.652, + 1.742 + ], + [ + 0, + 2.148 + ], + [ + 1.172, + 2.148 + ], + [ + 1.985, + 1.433 + ], + [ + 2.637, + 0.716 + ], + [ + 2.962, + 0 + ] + ], + "o": [ + [ + -2.637, + 0.716 + ], + [ + -2.019, + 1.449 + ], + [ + -1.189, + 2.132 + ], + [ + 0, + 2.116 + ], + [ + 0.602, + 1.758 + ], + [ + 1.025, + 1.449 + ], + [ + 1.399, + 1.189 + ], + [ + -1.888, + 0.912 + ], + [ + -1.66, + 1.205 + ], + [ + -1.237, + 1.562 + ], + [ + -0.716, + 1.953 + ], + [ + 0, + 2.962 + ], + [ + 1.057, + 2.377 + ], + [ + 2.051, + 1.628 + ], + [ + 2.995, + 0.879 + ], + [ + 3.548, + 0 + ], + [ + 2.946, + -0.911 + ], + [ + 2.116, + -1.692 + ], + [ + 1.172, + -2.409 + ], + [ + 0, + -2.376 + ], + [ + -0.847, + -1.92 + ], + [ + -1.4, + -1.546 + ], + [ + -1.807, + -1.237 + ], + [ + 1.823, + -0.911 + ], + [ + 1.562, + -1.106 + ], + [ + 1.139, + -1.416 + ], + [ + 0.651, + -1.741 + ], + [ + 0, + -2.897 + ], + [ + -1.172, + -2.148 + ], + [ + -1.986, + -1.432 + ], + [ + -2.637, + -0.716 + ], + [ + -2.898, + 0 + ] + ], + "v": [ + [ + 19.287, + -71.24 + ], + [ + 12.305, + -67.993 + ], + [ + 7.495, + -62.622 + ], + [ + 5.713, + -55.176 + ], + [ + 6.616, + -49.365 + ], + [ + 9.058, + -44.556 + ], + [ + 12.695, + -40.601 + ], + [ + 17.188, + -37.305 + ], + [ + 11.865, + -34.131 + ], + [ + 7.52, + -29.98 + ], + [ + 4.59, + -24.707 + ], + [ + 3.516, + -18.115 + ], + [ + 5.103, + -10.107 + ], + [ + 9.766, + -4.102 + ], + [ + 17.334, + -0.342 + ], + [ + 27.588, + 0.977 + ], + [ + 37.329, + -0.391 + ], + [ + 44.922, + -4.297 + ], + [ + 49.854, + -10.449 + ], + [ + 51.611, + -18.506 + ], + [ + 50.342, + -24.951 + ], + [ + 46.973, + -30.151 + ], + [ + 42.163, + -34.326 + ], + [ + 36.621, + -37.695 + ], + [ + 41.699, + -40.723 + ], + [ + 45.752, + -44.507 + ], + [ + 48.438, + -49.243 + ], + [ + 49.414, + -55.078 + ], + [ + 47.656, + -62.646 + ], + [ + 42.92, + -68.018 + ], + [ + 35.986, + -71.24 + ], + [ + 27.588, + -72.314 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "8", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.456, + 1.172 + ], + [ + -0.83, + 0.993 + ], + [ + -1.172, + 0.83 + ], + [ + -1.433, + 0.716 + ], + [ + 0, + 0 + ], + [ + -1.14, + -0.781 + ], + [ + -0.896, + -0.928 + ], + [ + -0.57, + -1.09 + ], + [ + 0, + -1.367 + ], + [ + 1.692, + -1.611 + ], + [ + 3.418, + 0 + ], + [ + 1.237, + 0.439 + ], + [ + 0.83, + 0.781 + ], + [ + 0.423, + 1.091 + ], + [ + 0, + 1.302 + ] + ], + "o": [ + [ + 0.455, + -1.172 + ], + [ + 0.83, + -0.993 + ], + [ + 1.172, + -0.83 + ], + [ + 0, + 0 + ], + [ + 1.139, + 0.716 + ], + [ + 1.139, + 0.781 + ], + [ + 0.895, + 0.928 + ], + [ + 0.569, + 1.091 + ], + [ + 0, + 2.637 + ], + [ + -1.693, + 1.611 + ], + [ + -1.628, + 0 + ], + [ + -1.237, + -0.439 + ], + [ + -0.83, + -0.781 + ], + [ + -0.423, + -1.09 + ], + [ + 0, + -1.367 + ] + ], + "v": [ + [ + 18.164, + -22.803 + ], + [ + 20.093, + -26.05 + ], + [ + 23.096, + -28.784 + ], + [ + 27.002, + -31.104 + ], + [ + 28.076, + -30.42 + ], + [ + 31.494, + -28.174 + ], + [ + 34.546, + -25.61 + ], + [ + 36.743, + -22.583 + ], + [ + 37.598, + -18.896 + ], + [ + 35.059, + -12.524 + ], + [ + 27.393, + -10.107 + ], + [ + 23.096, + -10.767 + ], + [ + 19.995, + -12.598 + ], + [ + 18.115, + -15.405 + ], + [ + 17.48, + -18.994 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "8", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 2, + "ty": "sh", + "ix": 3, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.945, + -0.325 + ], + [ + -0.684, + -0.618 + ], + [ + -0.391, + -0.911 + ], + [ + 0, + -1.139 + ], + [ + 0.423, + -1.009 + ], + [ + 0.7, + -0.797 + ], + [ + 0.911, + -0.618 + ], + [ + 1.009, + -0.52 + ], + [ + 0.928, + 0.603 + ], + [ + 0.7, + 0.765 + ], + [ + 0.407, + 0.993 + ], + [ + 0, + 1.367 + ], + [ + -0.391, + 0.912 + ], + [ + -0.668, + 0.619 + ], + [ + -0.928, + 0.326 + ], + [ + -1.074, + 0 + ] + ], + "o": [ + [ + 0.944, + 0.326 + ], + [ + 0.684, + 0.619 + ], + [ + 0.391, + 0.912 + ], + [ + 0, + 1.302 + ], + [ + -0.423, + 1.009 + ], + [ + -0.7, + 0.798 + ], + [ + -0.912, + 0.619 + ], + [ + -1.042, + -0.586 + ], + [ + -0.928, + -0.602 + ], + [ + -0.7, + -0.764 + ], + [ + -0.407, + -0.993 + ], + [ + 0, + -1.139 + ], + [ + 0.391, + -0.911 + ], + [ + 0.667, + -0.618 + ], + [ + 0.928, + -0.325 + ], + [ + 1.106, + 0 + ] + ], + "v": [ + [ + 30.566, + -60.791 + ], + [ + 33.008, + -59.375 + ], + [ + 34.619, + -57.08 + ], + [ + 35.205, + -54.004 + ], + [ + 34.57, + -50.537 + ], + [ + 32.886, + -47.827 + ], + [ + 30.469, + -45.703 + ], + [ + 27.588, + -43.994 + ], + [ + 24.634, + -45.776 + ], + [ + 22.192, + -47.827 + ], + [ + 20.532, + -50.464 + ], + [ + 19.922, + -54.004 + ], + [ + 20.508, + -57.08 + ], + [ + 22.095, + -59.375 + ], + [ + 24.487, + -60.791 + ], + [ + 27.49, + -61.279 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "8", + "mn": "ADBE Vector Shape - Group", + "hd": false + } + ], + "nm": "8", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ] + }, + "fFamily": "Noto Sans" + } + ] +} \ No newline at end of file diff --git a/lib/app/app_cubit/app_state.dart b/lib/app/app_cubit/app_state.dart index 4cb7c12..332ca0d 100644 --- a/lib/app/app_cubit/app_state.dart +++ b/lib/app/app_cubit/app_state.dart @@ -1,7 +1,7 @@ part of 'app_cubit.dart'; @freezed -class AppState { +class AppState with _$AppState { const factory AppState.standard() = _Standard; const factory AppState.networkChanged(AppNetworks newNetwork) = _NetworkChanged; diff --git a/lib/app/app_layout.dart b/lib/app/app_layout.dart index c75802a..b55ecc9 100644 --- a/lib/app/app_layout.dart +++ b/lib/app/app_layout.dart @@ -37,11 +37,7 @@ class _AppPageState extends State with DeviceInfoMixin { alignment: Alignment.bottomRight, child: Padding( padding: const EdgeInsets.all(20), - child: SelectionArea( - child: AppCookieConsentWidget( - onAccept: () => overlayEntry.remove(), - ), - ), + child: SelectionArea(child: AppCookieConsentWidget(onAccept: () => overlayEntry.remove())), ), ); }, @@ -73,19 +69,10 @@ class _AppPageState extends State with DeviceInfoMixin { title: AppHeader(height: appBarHeight), toolbarHeight: appBarHeight, ), - SliverToBoxAdapter( - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: MediaQuery.of(context).size.height - appBarHeight), - child: const RouterOutlet( - key: Key("screen"), - ), - ), - ), + const SliverFillRemaining(hasScrollBody: false, child: RouterOutlet(key: Key("screen"))), SliverToBoxAdapter( child: Padding( - padding: EdgeInsets.only( - bottom: shouldShowBottomNavigationBar ? AppBottomNavigationBar.height : 0, - ), + padding: EdgeInsets.only(bottom: shouldShowBottomNavigationBar ? AppBottomNavigationBar.height : 0), child: const AppFooter(), ), ), diff --git a/lib/app/create/create_page_select_tokens_stage.dart b/lib/app/create/create_page_select_tokens_stage.dart index 9bcc059..d45aa84 100644 --- a/lib/app/create/create_page_select_tokens_stage.dart +++ b/lib/app/create/create_page_select_tokens_stage.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:async/async.dart'; import 'package:flutter/material.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart'; @@ -28,8 +29,8 @@ class _CreatePageState extends State with DeviceInf final navigator = inject(); final cache = inject(); - late final token0SelectorController = TokenSelectorButtonController(initialSelectedToken: null); - final token1SelectorController = TokenSelectorButtonController(initialSelectedToken: null); + late final token0SelectorController = TokenSelectorButtonController(); + final token1SelectorController = TokenSelectorButtonController(); StreamSubscription? _token0SelectorStreamSubscription; StreamSubscription? _token1SelectorStreamSubscription; StreamSubscription? _selectedNetworkStreamSubscription; @@ -106,11 +107,17 @@ class _CreatePageState extends State with DeviceInf alignment: Alignment.topLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ ZupPageTitle(S.of(context).createPageTitle), - Text( - S.of(context).createPageDescription, - style: const TextStyle(fontSize: 14, color: ZupColors.gray), + SizedBox( + height: 58, + child: Text( + S.of(context).createPageDescription, + maxLines: 3, + + style: const TextStyle(fontSize: 14, color: ZupColors.gray), + ), ), const SizedBox(height: 20), SizedBox( @@ -124,99 +131,87 @@ class _CreatePageState extends State with DeviceInf offset: const Offset(0, 8), child: Text( S.of(context).token0, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: ZupColors.gray, - ), + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), ), ), - StatefulBuilder(builder: (context, localSetState) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - const ExchangesFilterDropdownButton(), - const SizedBox(width: 10), - Badge( - alignment: const Alignment(1.05, -1.05), - smallSize: cache.getPoolSearchSettings().isDefault ? 0 : 6, - backgroundColor: ZupColors.orange, - child: ZupMiniButton( - key: const Key("pool-search-settings-button"), - onPressed: (buttonContext) => CreatePageSettingsDropdown.show( - buttonContext, - onClose: () { - if (mounted) { - WidgetsBinding.instance.addPostFrameCallback((_) => localSetState(() {})); - } - }, - ), - title: S.of(context).createPageSelectTokensStageSearchSettings, - icon: Assets.icons.gear.svg( - height: 18, - colorFilter: const ColorFilter.mode(ZupColors.white, BlendMode.srcIn), + StatefulBuilder( + builder: (context, localSetState) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + const ExchangesFilterDropdownButton(), + const SizedBox(width: 10), + Badge( + alignment: const Alignment(1.05, -1.05), + smallSize: cache.getPoolSearchSettings().isDefault ? 0 : 6, + backgroundColor: ZupColors.orange, + child: ZupMiniButton( + key: const Key("pool-search-settings-button"), + onPressed: (buttonContext) => CreatePageSettingsDropdown.show( + buttonContext, + onClose: () { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) => localSetState(() {})); + } + }, + ), + title: S.of(context).createPageSelectTokensStageSearchSettings, + icon: Assets.icons.gear.svg( + height: 18, + colorFilter: const ColorFilter.mode(ZupColors.white, BlendMode.srcIn), + ), ), ), - ), - ], - ); - }) + ], + ); + }, + ), ], ), ), const SizedBox(height: 12), - TokenSelectorButton( - key: const Key("token-a-selector"), - controller: token0SelectorController, - ), + TokenSelectorButton(key: const Key("token-a-selector"), controller: token0SelectorController), const SizedBox(height: 10), Text( S.of(context).token1, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: ZupColors.gray, - ), + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), ), const SizedBox(height: 5), - TokenSelectorButton( - key: const Key("token-b-selector"), - controller: token1SelectorController, - ), + TokenSelectorButton(key: const Key("token-b-selector"), controller: token1SelectorController), const SizedBox(height: 20), StreamBuilder( - stream: token0SelectorController.selectedTokenStream, - builder: (context, _) { - return StreamBuilder( - stream: token1SelectorController.selectedTokenStream, - builder: (context, _) { - return ZupPrimaryButton( - key: const Key("search-button"), - height: 50, - fixedIcon: true, - alignCenter: true, - title: S.of(context).createPageShowMeTheMoney, - foregroundColor: ZupColors.white, - icon: Assets.icons.sparkleMagnifyingglass.svg(), - onPressed: token0SelectorController.selectedToken != null && - token1SelectorController.selectedToken != null - ? (buttonContext) { - return navigator.navigateToDeposit( - appCubit.selectedNetwork.isAllNetworks - ? token0SelectorController.selectedToken!.internalId! - : token0SelectorController.selectedToken!.addresses[appCubit.currentChainId]!, - appCubit.selectedNetwork.isAllNetworks - ? token1SelectorController.selectedToken!.internalId! - : token1SelectorController.selectedToken!.addresses[appCubit.currentChainId]!, - appCubit.selectedNetwork, - ); - } - : null, - mainAxisSize: MainAxisSize.max, - ); - }, - ); - }) + stream: StreamGroup.mergeBroadcast([ + token0SelectorController.selectionStream, + token1SelectorController.selectionStream, + ]), + builder: (context, _) { + return ZupPrimaryButton( + key: const Key("search-button"), + height: 50, + fixedIcon: true, + alignCenter: true, + title: S.of(context).createPageShowMeTheMoney, + foregroundColor: ZupColors.white, + icon: Assets.icons.sparkleMagnifyingglass.svg(), + onPressed: token0SelectorController.hasSelection && token1SelectorController.hasSelection + ? (buttonContext) { + return navigator.navigateToDeposit( + network: appCubit.selectedNetwork, + group0: token0SelectorController.selectedTokenGroup?.id, + group1: token1SelectorController.selectedTokenGroup?.id, + token0: (appCubit.selectedNetwork.isAllNetworks) + ? token0SelectorController.selectedToken?.internalId + : token0SelectorController.selectedToken?.addresses[appCubit.currentChainId], + token1: (appCubit.selectedNetwork.isAllNetworks) + ? token1SelectorController.selectedToken?.internalId + : token1SelectorController.selectedToken?.addresses[appCubit.currentChainId]!, + ); + } + : null, + mainAxisSize: MainAxisSize.max, + ); + }, + ), ], ), ), diff --git a/lib/app/create/deposit/deposit_cubit.dart b/lib/app/create/deposit/deposit_cubit.dart index 83b10ec..584c703 100644 --- a/lib/app/create/deposit/deposit_cubit.dart +++ b/lib/app/create/deposit/deposit_cubit.dart @@ -47,13 +47,11 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM BigInt? _latestPoolTick; YieldDto? _selectedYield; - YieldTimeFrame? _selectedYieldTimeframe; late final Stream selectedYieldStream = _selectedYieldStreamController.stream; late final Stream poolTickStream = _pooltickStreamController.stream; YieldDto? get selectedYield => _selectedYield; - YieldTimeFrame? get selectedYieldTimeframe => _selectedYieldTimeframe; BigInt? get latestPoolTick => _latestPoolTick; DepositSettingsDto get depositSettings => _cache.getDepositSettings(); PoolSearchSettingsDto get poolSearchSettings => _cache.getPoolSearchSettings(); @@ -67,14 +65,18 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM } Future getBestPools({ - required String token0AddressOrId, - required String token1AddressOrId, + required String? token0AddressOrId, + required String? token1AddressOrId, + required String? group0Id, + required String? group1Id, bool ignoreMinLiquidity = false, }) async { try { _zupAnalytics.logSearch( token0: token0AddressOrId, token1: token1AddressOrId, + group0: group0Id, + group1: group1Id, network: _appCubit.selectedNetwork.label, ); @@ -84,6 +86,8 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM blockedProtocolIds: _cache.blockedProtocolsIds, token0InternalId: token0AddressOrId, token1InternalId: token1AddressOrId, + group0Id: group0Id, + group1Id: group1Id, searchSettings: ignoreMinLiquidity ? poolSearchSettings.copyWith(minLiquidityUSD: 0) : poolSearchSettings, testnetMode: _appCubit.isTestnetMode, ) @@ -91,14 +95,14 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM blockedProtocolIds: _cache.blockedProtocolsIds, token0Address: token0AddressOrId, token1Address: token1AddressOrId, + group0Id: group0Id, + group1Id: group1Id, network: _appCubit.selectedNetwork, searchSettings: ignoreMinLiquidity ? poolSearchSettings.copyWith(minLiquidityUSD: 0) : poolSearchSettings, ); if (yields.isEmpty) { - return emit( - DepositState.noYields(filtersApplied: yields.filters), - ); + return emit(DepositState.noYields(filtersApplied: yields.filters)); } emit(DepositState.success(yields)); @@ -107,9 +111,8 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM } } - Future selectYield(YieldDto? yieldDto, YieldTimeFrame? yieldTimeFrame) async { + Future selectYield(YieldDto? yieldDto) async { _selectedYield = yieldDto; - _selectedYieldTimeframe = yieldTimeFrame; _selectedYieldStreamController.add(selectedYield); if (selectedYield != null) { @@ -129,10 +132,7 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM () => _poolService.getPoolTick(selectedYieldBeforeCall!), expiration: _poolTickExpiration - const Duration(seconds: 1), ignoreCache: forceRefresh, - key: poolTickCacheKey( - network: selectedYield!.network, - poolAddress: selectedYield!.poolAddress, - ), + key: poolTickCacheKey(network: selectedYield!.network, poolAddress: selectedYield!.poolAddress), ); if (selectedYieldBeforeCall != selectedYield) return await getSelectedPoolTick(); @@ -166,10 +166,7 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM Future saveDepositSettings(Slippage slippage, Duration deadline) async { await _cache.saveDepositSettings( - DepositSettingsDto( - deadlineMinutes: deadline.inMinutes, - maxSlippage: slippage.value.toDouble(), - ), + DepositSettingsDto(deadlineMinutes: deadline.inMinutes, maxSlippage: slippage.value.toDouble()), ); } diff --git a/lib/app/create/deposit/deposit_page.dart b/lib/app/create/deposit/deposit_page.dart index c1f448b..f73410e 100644 --- a/lib/app/create/deposit/deposit_page.dart +++ b/lib/app/create/deposit/deposit_page.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import 'package:lottie/lottie.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:web3kit/web3kit.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/app/create/deposit/deposit_cubit.dart'; @@ -21,6 +20,7 @@ import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/enums/zup_navigator_paths.dart'; import 'package:zup_app/core/extensions/num_extension.dart'; import 'package:zup_app/core/extensions/string_extension.dart'; @@ -34,6 +34,7 @@ import 'package:zup_app/core/slippage.dart'; import 'package:zup_app/core/v3_v4_pool_constants.dart'; import 'package:zup_app/core/zup_analytics.dart'; import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; import 'package:zup_app/gen/assets.gen.dart'; import 'package:zup_app/l10n/gen/app_localizations.dart'; import 'package:zup_app/widgets/yield_card.dart'; @@ -76,19 +77,36 @@ class _DepositPageState extends State final lottieClick = inject(instanceName: InjectInstanceNames.lottieClick); final lottieEmpty = inject(instanceName: InjectInstanceNames.lottieEmpty); final lottieRadar = inject(instanceName: InjectInstanceNames.lottieRadar); + final lottieNumbers = inject(instanceName: InjectInstanceNames.lottieNumbers); final lottieMatching = inject(instanceName: InjectInstanceNames.lottieMatching); final lottieSearching = inject(instanceName: InjectInstanceNames.lottieSearching); final baseTokenAmountController = TextEditingController(); final quoteTokenAmountController = TextEditingController(); + final yieldsPageController = PageController(initialPage: 0); final wallet = inject(); final selectRangeSectorKey = GlobalKey(); ZupNavigator get _navigator => inject(); DepositCubit get _cubit => context.read(); AppCubit get _appCubit => inject(); - String get token0Address => _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsName?.param0 ?? "") ?? ""; - String get token1Address => _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsName?.param1 ?? "") ?? ""; + + String? get token0Id { + return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().token0); + } + + String? get token1Id { + return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().token1); + } + + String? get group0Id { + return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().group0); + } + + String? get group1Id { + return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().group1); + } + TokenDto get baseToken { return areTokensReversed ? _cubit.selectedYield!.token1 : _cubit.selectedYield!.token0; } @@ -97,6 +115,7 @@ class _DepositPageState extends State return areTokensReversed ? _cubit.selectedYield!.token0 : _cubit.selectedYield!.token1; } + num currentYieldPage = 0; bool areTokensReversed = false; bool isMaxRangeInfinity = true; bool isMinRangeInfinity = true; @@ -107,6 +126,7 @@ class _DepositPageState extends State RangeController minRangeController = RangeController(); RangeController maxRangeController = RangeController(); StreamSubscription? _poolTickStreamSubscription; + YieldTimeFrame selectedYieldTimeFrame = YieldTimeFrame.day; late Slippage selectedSlippage = _cubit.depositSettings.slippage; late Duration selectedDeadline = _cubit.depositSettings.deadline; @@ -137,12 +157,12 @@ class _DepositPageState extends State if (_cubit.latestPoolTick == null) return (minPrice: false, maxPrice: false, any: false); final isMinPriceOutOfRange = !isMinRangeInfinity && (minPrice) > currentPrice; - final isMaxPriceOutOfRanfe = !isMaxRangeInfinity && (maxPrice) < currentPrice; + final isMaxPriceOutOfRange = !isMaxRangeInfinity && (maxPrice) < currentPrice; return ( minPrice: isMinPriceOutOfRange, - maxPrice: isMaxPriceOutOfRanfe, - any: isMinPriceOutOfRange || isMaxPriceOutOfRanfe + maxPrice: isMaxPriceOutOfRange, + any: isMinPriceOutOfRange || isMaxPriceOutOfRange, ); } @@ -180,8 +200,8 @@ class _DepositPageState extends State }); } - void selectYield(YieldDto? yieldDto, YieldTimeFrame? yieldTimeFrame) async { - _cubit.selectYield(yieldDto, yieldTimeFrame).then((_) => calculateDepositTokensAmount()); + void selectYield(YieldDto? yieldDto) async { + _cubit.selectYield(yieldDto).then((_) => calculateDepositTokensAmount()); if (yieldDto != null) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -240,21 +260,23 @@ class _DepositPageState extends State return areTokensReversed ? minTickPrice.priceAsQuoteToken : maxTickPrice.priceAsBaseToken; } - final newQuoteTokenAmount = Decimal.tryParse(calculateToken1AmountFromToken0( - double.tryParse(baseTokenAmountController.text) ?? 0, - currentPrice, - getMinPrice(), - getMaxPrice(), - ).toString()) - ?.toStringAsFixed(quoteToken.decimals[_cubit.selectedYield!.network.chainId]!); - - final newBaseTokenAmount = Decimal.tryParse(calculateToken0AmountFromToken1( - double.tryParse(quoteTokenAmountController.text) ?? 0, - currentPrice, - getMinPrice(), - getMaxPrice(), - ).toString()) - ?.toStringAsFixed(baseToken.decimals[_cubit.selectedYield!.network.chainId]!); + final newQuoteTokenAmount = Decimal.tryParse( + calculateToken1AmountFromToken0( + double.tryParse(baseTokenAmountController.text) ?? 0, + currentPrice, + getMinPrice(), + getMaxPrice(), + ).toString(), + )?.toStringAsFixed(quoteToken.decimals[_cubit.selectedYield!.network.chainId]!); + + final newBaseTokenAmount = Decimal.tryParse( + calculateToken0AmountFromToken1( + double.tryParse(quoteTokenAmountController.text) ?? 0, + currentPrice, + getMinPrice(), + getMaxPrice(), + ).toString(), + )?.toStringAsFixed(baseToken.decimals[_cubit.selectedYield!.network.chainId]!); if (isBaseTokenAmountUserInput) { if (newQuoteTokenAmount?.isEmptyOrZero ?? true) return quoteTokenAmountController.clear(); @@ -284,7 +306,7 @@ class _DepositPageState extends State return ( title: S.of(context).depositPageInvalidTokenAmount(tokenSymbol: baseToken.symbol), icon: null, - onPressed: null + onPressed: null, ); } @@ -292,7 +314,7 @@ class _DepositPageState extends State return ( title: S.of(context).depositPageInvalidTokenAmount(tokenSymbol: quoteToken.symbol), icon: null, - onPressed: null + onPressed: null, ); } @@ -300,7 +322,7 @@ class _DepositPageState extends State return ( title: S.of(context).depositPageInsufficientTokenBalance(tokenSymbol: baseToken.symbol), icon: null, - onPressed: null + onPressed: null, ); } @@ -308,7 +330,7 @@ class _DepositPageState extends State return ( title: S.of(context).depositPageInsufficientTokenBalance(tokenSymbol: quoteToken.symbol), icon: null, - onPressed: null + onPressed: null, ); } @@ -318,7 +340,7 @@ class _DepositPageState extends State onPressed: () { PreviewDepositModal( key: const Key("preview-deposit-modal"), - yieldTimeFrame: _cubit.selectedYieldTimeframe!, + yieldTimeFrame: selectedYieldTimeFrame, deadline: selectedDeadline, maxSlippage: selectedSlippage, currentYield: _cubit.selectedYield!, @@ -327,11 +349,8 @@ class _DepositPageState extends State token1DepositAmountController: areTokensReversed ? baseTokenAmountController : quoteTokenAmountController, maxPrice: (isInfinity: isMaxRangeInfinity, price: maxPrice), minPrice: (isInfinity: isMinRangeInfinity, price: minPrice), - ).show( - context, - currentPoolTick: _cubit.latestPoolTick ?? BigInt.zero, - ); - } + ).show(context, currentPoolTick: _cubit.latestPoolTick ?? BigInt.zero); + }, ); } @@ -339,17 +358,32 @@ class _DepositPageState extends State void initState() { _cubit.setup(); - final currentNetworkFromUrl = _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsName?.param3 ?? ""); + final currentNetworkFromUrl = + _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().network) ?? ""; + + yieldsPageController.addListener(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + final currentControllerPage = yieldsPageController.page!.toInt(); + if (currentControllerPage == currentYieldPage) return; + + setState(() => currentYieldPage = currentControllerPage); + }); + }); - if (currentNetworkFromUrl?.isNotEmpty ?? false) { - final currentNetwork = AppNetworks.fromValue(currentNetworkFromUrl!); + if (currentNetworkFromUrl.isNotEmpty) { + final currentNetwork = AppNetworks.fromValue(currentNetworkFromUrl); if (currentNetwork != null && currentNetwork != _appCubit.selectedNetwork) { _appCubit.updateAppNetwork(currentNetwork); } } WidgetsBinding.instance.addPostFrameCallback((_) { - _cubit.getBestPools(token0AddressOrId: token0Address, token1AddressOrId: token1Address); + _cubit.getBestPools( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ); }); _poolTickStreamSubscription = _cubit.poolTickStream.listen((poolTick) { @@ -382,76 +416,79 @@ class _DepositPageState extends State noYields: (filtersApplied) => _buildNoYieldsState(filtersApplied: filtersApplied), error: () => _buildErrorState(), success: (yields) => StreamBuilder( - stream: _cubit.selectedYieldStream, - builder: (context, selectedYieldSnapshot) { - return Padding( - padding: EdgeInsets.only(top: isMobileSize(context) ? 20 : 60), - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 600), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ZupTextButton( - key: const Key("back-button"), - onPressed: () => _navigator.navigateToNewPosition(), - icon: Assets.icons.arrowLeft.svg(), - label: S.of(context).depositPageBackButtonTitle, - ), - Row( - children: [ - ZupPageTitle(S.of(context).depositPageTitle), - const Spacer(), - const SizedBox(width: 14), - ZupPillButton( - key: const Key("deposit-settings-button"), - backgroundColor: selectedSlippage.riskBackgroundColor, - foregroundColor: selectedSlippage.riskForegroundColor, - title: selectedSlippage.value != DepositSettingsDto.defaultMaxSlippage - ? S.of(context).depositPagePercentSlippage( + stream: _cubit.selectedYieldStream, + builder: (context, selectedYieldSnapshot) { + return Padding( + padding: EdgeInsets.only(top: isMobileSize(context) ? 20 : 60), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 650), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ZupTextButton( + key: const Key("back-button"), + onPressed: () => _navigator.navigateToNewPosition(), + icon: Assets.icons.arrowLeft.svg(), + label: S.of(context).depositPageBackButtonTitle, + ), + Row( + children: [ + ZupPageTitle(S.of(context).depositPageTitle), + const Spacer(), + const SizedBox(width: 14), + ZupPillButton( + key: const Key("deposit-settings-button"), + backgroundColor: selectedSlippage.riskBackgroundColor, + foregroundColor: selectedSlippage.riskForegroundColor, + title: selectedSlippage.value != DepositSettingsDto.defaultMaxSlippage + ? S + .of(context) + .depositPagePercentSlippage( valuePercent: selectedSlippage.value.formatPercent, ) - : null, - onPressed: (buttonContext) => ZupPopover.show( - adjustment: const Offset(0, 10), - showBasedOnContext: buttonContext, - child: DepositSettingsDropdownChild( - context, - selectedDeadline: selectedDeadline, - selectedSlippage: selectedSlippage, - onSettingsChanged: (slippage, deadline) { - _cubit.saveDepositSettings(slippage, deadline); - - setState(() { - selectedDeadline = deadline; - selectedSlippage = slippage; - }); - }, - ), - ), - icon: Assets.icons.gear.svg( - colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), - height: 20, - width: 20, + : null, + onPressed: (buttonContext) => ZupPopover.show( + adjustment: const Offset(0, 10), + showBasedOnContext: buttonContext, + child: DepositSettingsDropdownChild( + context, + selectedDeadline: selectedDeadline, + selectedSlippage: selectedSlippage, + onSettingsChanged: (slippage, deadline) { + _cubit.saveDepositSettings(slippage, deadline); + + setState(() { + selectedDeadline = deadline; + selectedSlippage = slippage; + }); + }, ), ), - ], - ), - const SizedBox(height: 16), - _buildYieldSelectionSector(yields), - const SizedBox(height: 20), - if (selectedYieldSnapshot.data != null) ...[ - _buildSelectRangeSector(), - const SizedBox(height: 20), - _buildDepositSection(), + icon: Assets.icons.gear.svg( + colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + height: 20, + width: 20, + ), + ), ], - const SizedBox(height: 200) + ), + const SizedBox(height: 16), + _buildYieldSelectionSector(yields), + const SizedBox(height: 20), + if (selectedYieldSnapshot.data != null) ...[ + _buildSelectRangeSector(), + const SizedBox(height: 20), + _buildDepositSection(), ], - ), + const SizedBox(height: 200), + ], ), ), - ); - }), + ), + ); + }, + ), ); }, ), @@ -461,186 +498,308 @@ class _DepositPageState extends State Widget _sectionTitle(String title) => Text(title, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600)); Widget _buildNoYieldsState({required PoolSearchFiltersDto filtersApplied}) => Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 60), - SizedBox( - width: 400, - child: ZupInfoState( - icon: Transform.scale(scale: 3, child: lottieEmpty), - iconSize: 120, - title: S.of(context).depositPageEmptyStateTitle, - description: S.of(context).depositPageEmptyStateDescription, - helpButtonTitle: S.of(context).depositPageEmptyStateHelpButtonTitle, - helpButtonIcon: Assets.icons.arrowLeft.svg(), - onHelpButtonTap: () => _navigator.navigateToNewPosition(), - ), - ), - const SizedBox(height: 60), - if (filtersApplied.minTvlUsd > 0) - Text.rich( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 60), + SizedBox( + width: 400, + child: ZupInfoState( + icon: Transform.scale(scale: 3, child: lottieEmpty), + iconSize: 120, + title: S.of(context).depositPageEmptyStateTitle, + description: S.of(context).depositPageEmptyStateDescription, + helpButtonTitle: S.of(context).depositPageEmptyStateHelpButtonTitle, + helpButtonIcon: Assets.icons.arrowLeft.svg(), + onHelpButtonTap: () => _navigator.navigateToNewPosition(), + ), + ), + const SizedBox(height: 60), + if (filtersApplied.minTvlUsd > 0) + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: Assets.icons.infoCircle.svg( + colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), + ), + ), + ), TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: Assets.icons.infoCircle.svg( - colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), + text: S + .of(context) + .depositPageMinLiquiditySearchAlert( + minLiquidity: NumberFormat.compactSimpleCurrency().format( + _cubit.poolSearchSettings.minLiquidityUSD, ), ), - ), - TextSpan( - text: S.of(context).depositPageMinLiquiditySearchAlert( - minLiquidity: NumberFormat.compactSimpleCurrency().format( - _cubit.poolSearchSettings.minLiquidityUSD, - ), - ), - style: const TextStyle(color: ZupColors.gray, fontSize: 14), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Transform.translate( - offset: const Offset(-2, 0), - child: TextButton( - key: const Key("search-all-pools-button"), - onPressed: () => _cubit.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - ignoreMinLiquidity: true, - ), - style: ButtonStyle( - padding: WidgetStateProperty.all(const EdgeInsets.all(6)), - ), - child: Text( - S.of(context).depositPageTrySearchAllPools, - style: const TextStyle(color: ZupColors.brand, fontSize: 14, fontWeight: FontWeight.w500), - ), - ), + style: const TextStyle(color: ZupColors.gray, fontSize: 14), + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Transform.translate( + offset: const Offset(-2, 0), + child: TextButton( + key: const Key("search-all-pools-button"), + onPressed: () => _cubit.getBestPools( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ignoreMinLiquidity: true, + ), + style: ButtonStyle(padding: WidgetStateProperty.all(const EdgeInsets.all(6))), + child: Text( + S.of(context).depositPageTrySearchAllPools, + style: const TextStyle(color: ZupColors.brand, fontSize: 14, fontWeight: FontWeight.w500), ), - ) - ], + ), + ), ), - ), - ], - ), - ); - - Widget _buildErrorState() => Center( - child: SizedBox( - width: 400, - child: ZupInfoState( - icon: const IgnorePointer(child: Text(":(", style: TextStyle(color: ZupColors.brand))), - title: S.of(context).depositPageErrorStateTitle, - description: S.of(context).depositPageErrorStateDescription, - helpButtonTitle: S.of(context).letsGiveItAnotherShot, - helpButtonIcon: Assets.icons.arrowClockwise.svg(), - onHelpButtonTap: () => _cubit.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, + ], ), ), + ], + ), + ); + + Widget _buildErrorState() => Center( + child: SizedBox( + width: 400, + child: ZupInfoState( + icon: const IgnorePointer( + child: Text(":(", style: TextStyle(color: ZupColors.brand)), ), - ); + title: S.of(context).depositPageErrorStateTitle, + description: S.of(context).depositPageErrorStateDescription, + helpButtonTitle: S.of(context).letsGiveItAnotherShot, + helpButtonIcon: Assets.icons.arrowClockwise.svg(), + onHelpButtonTap: () => _cubit.getBestPools( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ), + ), + ), + ); - Widget _buildLoadingState() => Container( - color: ZupColors.white, - child: Center( - child: ZupSteppedLoading( - steps: [ - ZupSteppedLoadingStep( - title: S.of(context).depositPageLoadingStep1Title, - description: S.of(context).depositPageLoadingStep1Description, - icon: lottieMatching, - iconSize: 200, - ), - ZupSteppedLoadingStep( - title: S.of(context).depositPageLoadingStep2Title, - description: S.of(context).depositPageLoadingStep2Description, - icon: lottieRadar, - iconSize: 200, - ), - ZupSteppedLoadingStep( - title: S.of(context).depositPageLoadingStep3Title, - description: S.of(context).depositPageLoadingStep3Description, - icon: lottieSearching, - iconSize: 200, - ), - ], - ), + Widget _buildLoadingState() { + bool isGroupSearch = group0Id != null || group1Id != null; + + return Container( + color: ZupColors.white, + child: Center( + child: ZupSteppedLoading( + stepDuration: Duration(seconds: isGroupSearch ? 8 : 6), + steps: [ + ZupSteppedLoadingStep( + title: S.of(context).depositPageLoadingStep1Title, + description: S.of(context).depositPageLoadingStep1Description, + icon: lottieMatching, + iconSize: 200, + ), + ZupSteppedLoadingStep( + title: S.of(context).depositPageLoadingStep2Title, + description: S.of(context).depositPageLoadingStep2Description, + icon: lottieRadar, + iconSize: 200, + ), + ZupSteppedLoadingStep( + title: S.of(context).depositPageLoadingStep3Title, + description: S.of(context).depositPageLoadingStep3Description, + icon: lottieNumbers, + iconSize: 200, + ), + ZupSteppedLoadingStep( + title: S.of(context).depositPageLoadingStep4Title, + description: S.of(context).depositPageLoadingStep4Description, + icon: lottieSearching, + iconSize: 200, + ), + ], ), - ); + ), + ); + } Widget _buildYieldSelectionSector(YieldsDto yields) { - final best30dYield = yields.best30dYield; - final best24hYield = yields.best24hYield; - final best90dYield = yields.best90dYield; - - final List yieldCards = [ - YieldCard( - key: const Key("yield-card-24h"), - currentYield: best24hYield, - onChangeSelection: (yield) { - selectYield(yield, YieldTimeFrame.day); - }, - isSelected: _cubit.selectedYield.equals(best24hYield) && (_cubit.selectedYieldTimeframe?.isDay ?? false), - timeFrame: YieldTimeFrame.day, - ), - const SizedBox(width: 8, height: 20), - YieldCard( - key: const Key("yield-card-30d"), - currentYield: best30dYield, - onChangeSelection: (yield) { - selectYield(yield, YieldTimeFrame.month); - }, - isSelected: _cubit.selectedYield.equals(best30dYield) && (_cubit.selectedYieldTimeframe?.isMonth ?? false), - timeFrame: YieldTimeFrame.month, - ), - const SizedBox(width: 8, height: 20), - YieldCard( - key: const Key("yield-card-90d"), - currentYield: best90dYield, - onChangeSelection: (yield) { - selectYield(yield, YieldTimeFrame.threeMonth); - }, - isSelected: _cubit.selectedYield.equals(best90dYield) && (_cubit.selectedYieldTimeframe?.isThreeMonth ?? false), - timeFrame: YieldTimeFrame.threeMonth, - ), - ]; + final poolsCount = yields.poolsSortedByTimeframe(selectedYieldTimeFrame).length; + final yieldCardsPerPage = isMobileSize(context) ? 1 : 2; + final yieldsPagesCount = (poolsCount / yieldCardsPerPage).ceil(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ZupTooltip( - key: const Key("timeframe-tooltip"), - message: S.of(context).depositPageTimeFrameTooltipMessage, - helperButtonTitle: S.of(context).depositPageTimeFrameTooltipHelperButtonTitle, - helperButtonOnPressed: () { - launchUrl(Uri.parse("https://zupprotocol.substack.com/p/zup-timeframes-explained-why-you")); - }, - child: Row( - mainAxisSize: MainAxisSize.min, + Container( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), + decoration: BoxDecoration(color: ZupColors.gray6, borderRadius: BorderRadius.circular(12)), + child: Wrap( + runSpacing: 10, + crossAxisAlignment: WrapCrossAlignment.center, children: [ - IgnorePointer(child: _sectionTitle(S.of(context).depositPageTimeFrameTitle)), - const SizedBox(width: 8), - Assets.icons.infoCircle.svg( - colorFilter: const ColorFilter.mode( - ZupColors.gray, - BlendMode.srcIn, - ), + Text( + S.of(context).depositPageBestYieldsIn, + style: const TextStyle(color: ZupColors.black, fontSize: 14, fontWeight: FontWeight.w600), + ), + const SizedBox(width: 5), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoSlidingSegmentedControl( + proportionalWidth: true, + onValueChanged: (timeframe) { + setState(() { + selectedYieldTimeFrame = timeframe ?? YieldTimeFrame.day; + }); + + yieldsPageController.jumpToPage(0); + }, + groupValue: selectedYieldTimeFrame, + children: Map.fromEntries( + YieldTimeFrame.values.map( + (timeframe) => MapEntry( + timeframe, + IgnorePointer( + key: Key("${timeframe.name}-timeframe-button"), + child: Text( + timeframe.compactDaysLabel(context), + style: const TextStyle( + color: ZupColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ) + .animatedHover(animationValue: 0.2, type: ZupAnimatedHoverType.opacity) + .animatedHover(animationValue: 0.95, type: ZupAnimatedHoverType.scale), + ), + ), + ), + ), + const SizedBox(width: 10), + ZupTooltip.text( + key: const Key("timeframe-tooltip"), + message: S.of(context).depositPageTimeFrameTooltipMessage, + child: Assets.icons.infoCircle.svg( + colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), + ), + ), + ], ), ], ), ), + const SizedBox(height: 5), + + SizedBox( + height: 150, + child: PageView.builder( + physics: const NeverScrollableScrollPhysics(), + controller: yieldsPageController, + pageSnapping: false, + padEnds: false, + scrollDirection: Axis.horizontal, + itemCount: yieldsPagesCount, + itemBuilder: (_, pageIndex) { + final startIndex = pageIndex * yieldCardsPerPage; + final endIndex = (startIndex + yieldCardsPerPage).clamp(0, poolsCount); + + final yieldsInThisPage = yields + .poolsSortedByTimeframe(selectedYieldTimeFrame) + .sublist(startIndex, endIndex); + + return Padding( + padding: const EdgeInsets.all(5), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 5, + children: yieldsInThisPage + .map( + (yieldItem) => Expanded( + child: YieldCard( + key: Key("yield-card-${yieldItem.poolAddress}"), + isHotestYield: yieldItem.equals( + yields.poolsSortedByTimeframe(selectedYieldTimeFrame).first, + ), + currentYield: yieldItem, + onChangeSelection: (yield) { + selectYield(yield); + }, + isSelected: _cubit.selectedYield.equals(yieldItem), + timeFrame: selectedYieldTimeFrame, + ), + ), + ) + .toList(), + ), + ); + }, + ), + ), const SizedBox(height: 10), - isMobileSize(context) - ? Column(children: yieldCards) - : Row( - children: yieldCards.map((x) { - if (x is YieldCard) return Expanded(child: x); - return x; - }).toList()), + Row( + children: [ + ZupIconButton( + key: const Key("previous-yield-page-button"), + icon: Assets.icons.arrowLeft.svg(height: 12, width: 12), + padding: const EdgeInsets.all(10), + onPressed: (_) async { + yieldsPageController.previousPage( + duration: const Duration(milliseconds: 400), + curve: Curves.fastEaseInToSlowEaseOut, + ); + }, + ), + const SizedBox(width: 10), + + ZupIconButton( + key: const Key("next-yield-page-button"), + padding: const EdgeInsets.all(10), + icon: Assets.icons.arrowRight.svg(height: 12, width: 12), + onPressed: (_) async { + yieldsPageController.nextPage( + duration: const Duration(milliseconds: 400), + curve: Curves.fastEaseInToSlowEaseOut, + ); + }, + ), + const Spacer(), + + Row( + children: List.generate( + (currentYieldPage + 4).clamp(0, yieldsPagesCount).ceil(), + (index) => AnimatedContainer( + key: Key("yield-page-indicator-$index"), + duration: const Duration(milliseconds: 200), + height: (index != currentYieldPage) ? 8 : 12, + width: (index != currentYieldPage) ? 8 : 12, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () async { + yieldsPageController.animateToPage( + index, + duration: const Duration(milliseconds: 600), + curve: Curves.decelerate, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: CircleAvatar( + backgroundColor: (currentYieldPage.truncate() == index) ? ZupColors.brand : ZupColors.gray5, + ), + ), + ).animatedHover(animationValue: index != currentYieldPage ? 4 : 1), + ), + ), + ), + ), + ], + ), const SizedBox(height: 10), if (_cubit.poolSearchSettings.minLiquidityUSD > 0) Wrap( @@ -670,20 +829,23 @@ class _DepositPageState extends State child: TextButton( key: const Key("hide-show-all-pools-button"), onPressed: () => _cubit.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, ignoreMinLiquidity: yields.filters.minTvlUsd > 0, ), - style: ButtonStyle( - padding: WidgetStateProperty.all(const EdgeInsets.all(6)), - ), + style: ButtonStyle(padding: WidgetStateProperty.all(const EdgeInsets.all(6))), child: Text( yields.filters.minTvlUsd > 0 ? S.of(context).depositPageSearchAllPools - : S.of(context).depositPageSearchOnlyForPoolsWithMorethan( - minLiquidity: NumberFormat.compactSimpleCurrency() - .format(_cubit.poolSearchSettings.minLiquidityUSD), - ), + : S + .of(context) + .depositPageSearchOnlyForPoolsWithMorethan( + minLiquidity: NumberFormat.compactSimpleCurrency().format( + _cubit.poolSearchSettings.minLiquidityUSD, + ), + ), style: const TextStyle(color: ZupColors.brand, fontSize: 14, fontWeight: FontWeight.w600), ), ), @@ -699,8 +861,8 @@ class _DepositPageState extends State title: S.of(context).depositPageNoYieldSelectedTitle, description: S.of(context).depositPageNoYieldSelectedDescription, ), - ) - ] + ), + ], ], ); } @@ -724,11 +886,12 @@ class _DepositPageState extends State key: const Key("reverse-tokens-reversed"), cursor: SystemMouseCursors.click, child: IgnorePointer( - ignoring: true, - child: Text( - "${_cubit.selectedYield?.token1.symbol} / ${_cubit.selectedYield?.token0.symbol}", - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), - )), + ignoring: true, + child: Text( + "${_cubit.selectedYield?.token1.symbol} / ${_cubit.selectedYield?.token0.symbol}", + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + ), ), }, onValueChanged: (isReversed) { @@ -745,34 +908,25 @@ class _DepositPageState extends State _sectionTitle(S.of(context).depositPageRangeSectionTitle), const SizedBox(width: 12), const Spacer(), - if (!isMobileSize(context)) tokenSwitcher + if (!isMobileSize(context)) tokenSwitcher, ], ), - if (isMobileSize(context)) ...[ - const SizedBox(height: 5), - tokenSwitcher, - const SizedBox(height: 5), - ], + if (isMobileSize(context)) ...[const SizedBox(height: 5), tokenSwitcher, const SizedBox(height: 5)], const SizedBox(height: 10), StreamBuilder( - stream: _cubit.poolTickStream, - initialData: _cubit.latestPoolTick, - builder: (context, poolTickSnapshot) { - return Text( - "1 ${baseToken.symbol} ≈ ${() { - final currentPrice = tickToPrice( - tick: poolTickSnapshot.data ?? BigInt.zero, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - ); - - return areTokensReversed ? currentPrice.priceAsQuoteToken : currentPrice.priceAsBaseToken; - }.call().formatCurrency(useLessThan: true, maxDecimals: 4, isUSD: false)} ${quoteToken.symbol}", - style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500)) - .redacted( - enabled: poolTickSnapshot.data == null, - ); - }), + stream: _cubit.poolTickStream, + initialData: _cubit.latestPoolTick, + builder: (context, poolTickSnapshot) { + return Text( + "1 ${baseToken.symbol} ≈ ${() { + final currentPrice = tickToPrice(tick: poolTickSnapshot.data ?? BigInt.zero, poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals); + + return areTokensReversed ? currentPrice.priceAsQuoteToken : currentPrice.priceAsBaseToken; + }.call().formatCurrency(useLessThan: true, maxDecimals: 4, isUSD: false)} ${quoteToken.symbol}", + style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500), + ).redacted(enabled: poolTickSnapshot.data == null); + }, + ), const SizedBox(height: 10), Wrap( spacing: 10, @@ -813,220 +967,222 @@ class _DepositPageState extends State ), const SizedBox(height: 10), StreamBuilder( - stream: _cubit.poolTickStream, - builder: (context, snapshot) { - return RangeSelector( - key: const Key("min-price-selector"), - onUserType: () => percentRange = null, - onPriceChanged: (price) { - setState(() { - if (price == 0) { - isMinRangeInfinity = true; - - return calculateDepositTokensAmount(); - } - - isMinRangeInfinity = false; - minPrice = price; - calculateDepositTokensAmount(); - }); - }, - initialPrice: minPrice, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - isReversed: areTokensReversed, - displayBaseTokenSymbol: baseToken.symbol, - displayQuoteTokenSymbol: quoteToken.symbol, - tickSpacing: _cubit.selectedYield!.tickSpacing, - type: RangeSelectorType.minPrice, - isInfinity: isMinRangeInfinity, - rangeController: minRangeController, - state: () { - if (isOutOfRange.minPrice) { - return RangeSelectorState( - type: RangeSelectorStateType.warning, - message: S.of(context).depositPageMinRangeOutOfRangeWarningText, - ); + stream: _cubit.poolTickStream, + builder: (context, snapshot) { + return RangeSelector( + key: const Key("min-price-selector"), + onUserType: () => percentRange = null, + onPriceChanged: (price) { + setState(() { + if (price == 0) { + isMinRangeInfinity = true; + + return calculateDepositTokensAmount(); } - return const RangeSelectorState(type: RangeSelectorStateType.regular); - }.call(), - ); - }), + isMinRangeInfinity = false; + minPrice = price; + calculateDepositTokensAmount(); + }); + }, + initialPrice: minPrice, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, + isReversed: areTokensReversed, + displayBaseTokenSymbol: baseToken.symbol, + displayQuoteTokenSymbol: quoteToken.symbol, + tickSpacing: _cubit.selectedYield!.tickSpacing, + type: RangeSelectorType.minPrice, + isInfinity: isMinRangeInfinity, + rangeController: minRangeController, + state: () { + if (isOutOfRange.minPrice) { + return RangeSelectorState( + type: RangeSelectorStateType.warning, + message: S.of(context).depositPageMinRangeOutOfRangeWarningText, + ); + } + + return const RangeSelectorState(type: RangeSelectorStateType.regular); + }.call(), + ); + }, + ), const SizedBox(height: 6), StreamBuilder( - stream: _cubit.poolTickStream, - builder: (context, snapshot) { - return RangeSelector( - key: const Key("max-price-selector"), - displayBaseTokenSymbol: baseToken.symbol, - displayQuoteTokenSymbol: quoteToken.symbol, - onUserType: () => percentRange = null, - onPriceChanged: (price) { - setState(() { - if (price == 0) { - isMaxRangeInfinity = true; + stream: _cubit.poolTickStream, + builder: (context, snapshot) { + return RangeSelector( + key: const Key("max-price-selector"), + displayBaseTokenSymbol: baseToken.symbol, + displayQuoteTokenSymbol: quoteToken.symbol, + onUserType: () => percentRange = null, + onPriceChanged: (price) { + setState(() { + if (price == 0) { + isMaxRangeInfinity = true; + + return calculateDepositTokensAmount(); + } + + isMaxRangeInfinity = false; + maxPrice = price; + + calculateDepositTokensAmount(); + }); + }, + type: RangeSelectorType.maxPrice, + isInfinity: isMaxRangeInfinity, + initialPrice: maxPrice, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, + isReversed: areTokensReversed, + tickSpacing: _cubit.selectedYield!.tickSpacing, + rangeController: maxRangeController, + state: () { + if (isRangeInvalid) { + return RangeSelectorState( + type: RangeSelectorStateType.error, + message: S.of(context).depositPageInvalidRangeErrorText, + ); + } + + if (isOutOfRange.maxPrice) { + return RangeSelectorState( + type: RangeSelectorStateType.warning, + message: S.of(context).depositPageMaxRangeOutOfRangeWarningText, + ); + } + + return const RangeSelectorState(type: RangeSelectorStateType.regular); + }.call(), + ); + }, + ), + ], + ); + } - return calculateDepositTokensAmount(); - } + Widget _buildDepositSection() => IgnorePointer( + key: const Key("deposit-section"), + ignoring: isRangeInvalid, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: isRangeInvalid ? 0.2 : 1, + child: StreamBuilder( + stream: _cubit.poolTickStream, + initialData: _cubit.latestPoolTick, + builder: (context, poolTickSnapshot) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionTitle(S.of(context).depositPageDepositSectionTitle), + const SizedBox(height: 12), + TokenAmountInputCard( + key: const Key("base-token-input-card"), + token: baseToken, + isNative: baseToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( + EthereumConstants.zeroAddress, + ), + onRefreshBalance: () => setState(() {}), + disabledText: () { + if (!isBaseTokenNeeded) { + return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: baseToken.symbol); + } - isMaxRangeInfinity = false; - maxPrice = price; + if (!isBaseTokenAmountUserInput && + !poolTickSnapshot.hasData && + quoteTokenAmountController.text.isNotEmpty) { + return S.of(context).loading; + } + }.call(), + onInput: (amount) { + setState(() { + isBaseTokenAmountUserInput = true; calculateDepositTokensAmount(); }); }, - type: RangeSelectorType.maxPrice, - isInfinity: isMaxRangeInfinity, - initialPrice: maxPrice, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - isReversed: areTokensReversed, - tickSpacing: _cubit.selectedYield!.tickSpacing, - rangeController: maxRangeController, - state: () { - if (isRangeInvalid) { - return RangeSelectorState( - type: RangeSelectorStateType.error, - message: S.of(context).depositPageInvalidRangeErrorText, - ); + controller: baseTokenAmountController, + network: _cubit.selectedYield!.network, + ), + const SizedBox(height: 6), + TokenAmountInputCard( + key: const Key("quote-token-input-card"), + token: quoteToken, + isNative: quoteToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( + EthereumConstants.zeroAddress, + ), + onRefreshBalance: () => setState(() {}), + disabledText: () { + if (!isQuoteTokenNeeded) { + return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: quoteToken.symbol); } - if (isOutOfRange.maxPrice) { - return RangeSelectorState( - type: RangeSelectorStateType.warning, - message: S.of(context).depositPageMaxRangeOutOfRangeWarningText, - ); + if (isBaseTokenAmountUserInput && + !poolTickSnapshot.hasData && + baseTokenAmountController.text.isNotEmpty) { + return S.of(context).loading; } - - return const RangeSelectorState(type: RangeSelectorStateType.regular); }.call(), - ); - }), - ], - ); - } + onInput: (amount) { + setState(() { + isBaseTokenAmountUserInput = false; - Widget _buildDepositSection() => IgnorePointer( - key: const Key("deposit-section"), - ignoring: isRangeInvalid, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 300), - opacity: isRangeInvalid ? 0.2 : 1, - child: StreamBuilder( - stream: _cubit.poolTickStream, - initialData: _cubit.latestPoolTick, - builder: (context, poolTickSnapshot) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + calculateDepositTokensAmount(); + }); + }, + controller: quoteTokenAmountController, + network: _cubit.selectedYield!.network, + ), + const SizedBox(height: 20), + Row( children: [ - _sectionTitle(S.of(context).depositPageDepositSectionTitle), - const SizedBox(height: 12), - TokenAmountInputCard( - key: const Key("base-token-input-card"), - token: baseToken, - isNative: baseToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( - EthereumConstants.zeroAddress, - ), - onRefreshBalance: () => setState(() {}), - disabledText: () { - if (!isBaseTokenNeeded) { - return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: baseToken.symbol); - } - - if (!isBaseTokenAmountUserInput && - !poolTickSnapshot.hasData && - quoteTokenAmountController.text.isNotEmpty) { - return S.of(context).loading; - } - }.call(), - onInput: (amount) { - setState(() { - isBaseTokenAmountUserInput = true; - - calculateDepositTokensAmount(); - }); - }, - controller: baseTokenAmountController, - network: _cubit.selectedYield!.network, - ), - const SizedBox(height: 6), - TokenAmountInputCard( - key: const Key("quote-token-input-card"), - token: quoteToken, - isNative: quoteToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( - EthereumConstants.zeroAddress, - ), - onRefreshBalance: () => setState(() {}), - disabledText: () { - if (!isQuoteTokenNeeded) { - return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: quoteToken.symbol); - } - - if (isBaseTokenAmountUserInput && - !poolTickSnapshot.hasData && - baseTokenAmountController.text.isNotEmpty) { - return S.of(context).loading; - } - }.call(), - onInput: (amount) { - setState(() { - isBaseTokenAmountUserInput = false; - - calculateDepositTokensAmount(); - }); - }, - controller: quoteTokenAmountController, - network: _cubit.selectedYield!.network, - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: StreamBuilder( - key: const Key("deposit-button"), - stream: wallet.signerStream, - initialData: wallet.signer, - builder: (context, signerSnapshot) { - if (!signerSnapshot.hasData) { - return ZupPrimaryButton( - width: double.maxFinite, - title: S.of(context).connectWallet, - icon: Assets.icons.walletBifold.svg(), - fixedIcon: true, - alignCenter: true, - hoverElevation: 0, - backgroundColor: ZupColors.brand7, - foregroundColor: ZupColors.brand, - onPressed: (buttonContext) => ConnectModal().show(context), - ); - } - - return FutureBuilder( - future: depositButtonState(), - builder: (context, stateSnapshot) { - return ZupPrimaryButton( - alignCenter: true, - title: stateSnapshot.data?.title ?? "Loading...", - icon: stateSnapshot.data?.icon, - isLoading: stateSnapshot.connectionState == ConnectionState.waiting, - fixedIcon: true, - onPressed: stateSnapshot.data?.onPressed == null - ? null - : (buttonContext) => stateSnapshot.data?.onPressed!(), - width: double.maxFinite, - ); - }, + Expanded( + child: StreamBuilder( + key: const Key("deposit-button"), + stream: wallet.signerStream, + initialData: wallet.signer, + builder: (context, signerSnapshot) { + if (!signerSnapshot.hasData) { + return ZupPrimaryButton( + width: double.maxFinite, + title: S.of(context).connectWallet, + icon: Assets.icons.walletBifold.svg(), + fixedIcon: true, + alignCenter: true, + hoverElevation: 0, + backgroundColor: ZupColors.brand7, + foregroundColor: ZupColors.brand, + onPressed: (buttonContext) => ConnectModal().show(context), + ); + } + + return FutureBuilder( + future: depositButtonState(), + builder: (context, stateSnapshot) { + return ZupPrimaryButton( + alignCenter: true, + title: stateSnapshot.data?.title ?? "Loading...", + icon: stateSnapshot.data?.icon, + isLoading: stateSnapshot.connectionState == ConnectionState.waiting, + fixedIcon: true, + onPressed: stateSnapshot.data?.onPressed == null + ? null + : (buttonContext) => stateSnapshot.data?.onPressed!(), + width: double.maxFinite, ); }, - ), - ), - ], + ); + }, + ), ), ], - ); - }, - ), - ), - ); + ), + ], + ); + }, + ), + ), + ); } diff --git a/lib/app/create/deposit/widgets/deposit_settings_dropdown_child.dart b/lib/app/create/deposit/widgets/deposit_settings_dropdown_child.dart index 37331e5..45ffdff 100644 --- a/lib/app/create/deposit/widgets/deposit_settings_dropdown_child.dart +++ b/lib/app/create/deposit/widgets/deposit_settings_dropdown_child.dart @@ -67,7 +67,7 @@ class _DepositSettingsDropdownChildState extends State with V3PoolCo ? widget.token0DepositAmountController.parseTextToDouble : widget.token1DepositAmountController.parseTextToDouble; - BigInt get token0DepositAmount => widget.token0DepositAmountController.parseTextToDouble - .parseTokenAmount(decimals: widget.currentYield.token0NetworkDecimals); + BigInt get token0DepositAmount => widget.token0DepositAmountController.parseTextToDouble.parseTokenAmount( + decimals: widget.currentYield.token0NetworkDecimals, + ); - BigInt get token1DepositAmount => widget.token1DepositAmountController.parseTextToDouble - .parseTokenAmount(decimals: widget.currentYield.token1NetworkDecimals); + BigInt get token1DepositAmount => widget.token1DepositAmountController.parseTextToDouble.parseTokenAmount( + decimals: widget.currentYield.token1NetworkDecimals, + ); double get currentPrice { final currentTick = cubit.latestPoolTick; @@ -162,10 +165,10 @@ class _PreviewDepositModalState extends State with V3PoolCo } ({double priceAsBaseToken, double priceAsQuoteToken}) price() => tickToPrice( - tick: tick(), - poolToken0Decimals: widget.currentYield.token0NetworkDecimals, - poolToken1Decimals: widget.currentYield.token1NetworkDecimals, - ); + tick: tick(), + poolToken0Decimals: widget.currentYield.token0NetworkDecimals, + poolToken1Decimals: widget.currentYield.token1NetworkDecimals, + ); return isReversedLocal ? price().priceAsQuoteToken : price().priceAsBaseToken; } @@ -183,26 +186,14 @@ class _PreviewDepositModalState extends State with V3PoolCo } ({double priceAsBaseToken, double priceAsQuoteToken}) price() => tickToPrice( - tick: tick(), - poolToken0Decimals: widget.currentYield.token0NetworkDecimals, - poolToken1Decimals: widget.currentYield.token1NetworkDecimals, - ); + tick: tick(), + poolToken0Decimals: widget.currentYield.token0NetworkDecimals, + poolToken1Decimals: widget.currentYield.token1NetworkDecimals, + ); return isReversedLocal ? price().priceAsQuoteToken : price().priceAsBaseToken; } - num get yieldTimeframed { - if (widget.yieldTimeFrame.isDay) { - return widget.currentYield.yield24h; - } - - if (widget.yieldTimeFrame.isMonth) { - return widget.currentYield.yield30d; - } - - return widget.currentYield.yield90d; - } - ({bool minPrice, bool maxPrice, bool any}) get isOutOfRange { final isMinPriceOutOfRange = !widget.minPrice.isInfinity && (minPrice) > currentPrice; final isMaxPriceOutOfRanfe = !widget.maxPrice.isInfinity && (maxPrice) < currentPrice; @@ -210,24 +201,15 @@ class _PreviewDepositModalState extends State with V3PoolCo return ( minPrice: isMinPriceOutOfRange, maxPrice: isMaxPriceOutOfRanfe, - any: isMinPriceOutOfRange || isMaxPriceOutOfRanfe + any: isMinPriceOutOfRange || isMaxPriceOutOfRanfe, ); } ({String title, Widget? icon, Function()? onPressed, bool? isLoading}) get depositButtonState { return cubit.state.maybeWhen( - loading: () => ( - title: S.of(context).loading, - icon: null, - onPressed: null, - isLoading: true, - ), - waitingTransaction: (txId, type) => ( - title: S.of(context).previewDepositModalWaitingTransaction, - icon: null, - onPressed: null, - isLoading: true, - ), + loading: () => (title: S.of(context).loading, icon: null, onPressed: null, isLoading: true), + waitingTransaction: (txId, type) => + (title: S.of(context).previewDepositModalWaitingTransaction, icon: null, onPressed: null, isLoading: true), approvingToken: (symbol) => ( title: S.of(context).previewDepositModalApprovingToken(tokenSymbol: symbol), icon: null, @@ -235,7 +217,9 @@ class _PreviewDepositModalState extends State with V3PoolCo isLoading: true, ), depositing: () => ( - title: S.of(context).previewDepositModalDepositingIntoPool( + title: S + .of(context) + .previewDepositModalDepositingIntoPool( baseTokenSymbol: baseToken.symbol, quoteTokenSymbol: quoteToken.symbol, ), @@ -250,7 +234,7 @@ class _PreviewDepositModalState extends State with V3PoolCo title: S.of(context).previewDepositModalApproveToken(tokenSymbol: widget.currentYield.token0.symbol), icon: Assets.icons.lockOpen.svg(), isLoading: false, - onPressed: () => cubit.approveToken(widget.currentYield.token0, token0DepositAmount) + onPressed: () => cubit.approveToken(widget.currentYield.token0, token0DepositAmount), ); } } @@ -261,7 +245,7 @@ class _PreviewDepositModalState extends State with V3PoolCo title: S.of(context).previewDepositModalApproveToken(tokenSymbol: widget.currentYield.token1.symbol), icon: Assets.icons.lockOpen.svg(), isLoading: false, - onPressed: () => cubit.approveToken(widget.currentYield.token1, token1DepositAmount) + onPressed: () => cubit.approveToken(widget.currentYield.token1, token1DepositAmount), ); } } @@ -271,60 +255,47 @@ class _PreviewDepositModalState extends State with V3PoolCo icon: Assets.icons.paperplaneFill.svg(), isLoading: false, onPressed: () => cubit.deposit( - deadline: widget.deadline, - slippage: widget.maxSlippage, - token0Amount: token0DepositAmount, - token1Amount: token1DepositAmount, - minPrice: widget.minPrice.price, - maxPrice: widget.maxPrice.price, - isMinPriceInfinity: widget.minPrice.isInfinity, - isMaxPriceInfinity: widget.maxPrice.isInfinity, - isReversed: widget.isReversed, - ), + deadline: widget.deadline, + slippage: widget.maxSlippage, + token0Amount: token0DepositAmount, + token1Amount: token1DepositAmount, + minPrice: widget.minPrice.price, + maxPrice: widget.maxPrice.price, + isMinPriceInfinity: widget.minPrice.isInfinity, + isMaxPriceInfinity: widget.maxPrice.isInfinity, + isReversed: widget.isReversed, + ), ); }, - depositSuccess: (txId) => ( - title: S.of(context).loading, - icon: null, - isLoading: true, - onPressed: null, - ), - orElse: () => ( - title: S.of(context).previewDepositModalError, - icon: null, - onPressed: null, - isLoading: false, - ), + depositSuccess: (txId) => (title: S.of(context).loading, icon: null, isLoading: true, onPressed: null), + orElse: () => (title: S.of(context).previewDepositModalError, icon: null, onPressed: null, isLoading: false), ); } late bool isReversedLocal = widget.isReversed; Widget _sectionTitle(String title) => Text( - title, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: ZupColors.gray), - ); + title, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: ZupColors.gray), + ); Widget _fieldColumn({required String title, Widget? image, required String value, double spacing = 8}) => Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionTitle(title), + SizedBox(height: spacing), + Row( + mainAxisSize: MainAxisSize.min, children: [ - _sectionTitle(title), - SizedBox(height: spacing), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (image != null) ...[ - image, - const SizedBox(width: 8), - ], - Flexible( - fit: FlexFit.loose, - child: Text(value, overflow: TextOverflow.ellipsis), - ), - ], + if (image != null) ...[image, const SizedBox(width: 8)], + Flexible( + fit: FlexFit.loose, + child: Text(value, overflow: TextOverflow.ellipsis), ), ], - ); + ), + ], + ); @override void initState() { @@ -356,7 +327,7 @@ class _PreviewDepositModalState extends State with V3PoolCo type: ZupSnackBarType.info, helperButton: ( title: S.of(context).previewDepositModalWaitingTransactionSnackBarHelperButtonTitle, - onButtonTap: () => widget.currentYield.network.openTx(txId) + onButtonTap: () => widget.currentYield.network.openTx(txId), ), customIcon: const ZupCircularLoadingIndicator(size: 20), snackDuration: const Duration(minutes: 10), @@ -368,9 +339,7 @@ class _PreviewDepositModalState extends State with V3PoolCo ScaffoldMessenger.of(context).showSnackBar( ZupSnackBar( context, - message: S.of(context).previewDepositModalApproveSuccessSnackBarMessage( - tokenSymbol: tokenSymbol, - ), + message: S.of(context).previewDepositModalApproveSuccessSnackBarMessage(tokenSymbol: tokenSymbol), type: ZupSnackBarType.success, helperButton: ( title: S.of(context).previewDepositModalApproveSuccessSnackBarHelperButtonTitle, @@ -409,7 +378,7 @@ class _PreviewDepositModalState extends State with V3PoolCo context, helperButton: ( title: S.of(context).previewDepositModalTransactionErrorSnackBarHelperButtonTitle, - onButtonTap: () => zupLinks.launchZupContactUs() + onButtonTap: () => zupLinks.launchZupContactUs(), ), message: S.of(context).previewDepositModalTransactionErrorSnackBarMessage, type: ZupSnackBarType.error, @@ -446,15 +415,16 @@ class _PreviewDepositModalState extends State with V3PoolCo ), const SizedBox(width: 10), StreamBuilder( - stream: cubit.poolTickStream, - builder: (context, tickSnapshot) { - return ZupTag( - title: isOutOfRange.any - ? S.of(context).previewDepositModalOutOfRange - : S.of(context).previewDepositModalInRange, - color: isOutOfRange.any ? ZupColors.orange : ZupColors.green, - ); - }), + stream: cubit.poolTickStream, + builder: (context, tickSnapshot) { + return ZupTag( + title: isOutOfRange.any + ? S.of(context).previewDepositModalOutOfRange + : S.of(context).previewDepositModalInRange, + color: isOutOfRange.any ? ZupColors.orange : ZupColors.green, + ); + }, + ), const Spacer(), ], ), @@ -483,10 +453,7 @@ class _PreviewDepositModalState extends State with V3PoolCo height: 16, child: Text( "${widget.currentYield.token1.symbol} / ${widget.currentYield.token0.symbol}", - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - ), + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), ), ), ), @@ -515,12 +482,9 @@ class _PreviewDepositModalState extends State with V3PoolCo Text(baseToken.symbol), const Spacer(), Text( - "${baseTokenAmount.maybeFormatCompactCurrency( - isUSD: false, - useLessThan: false, - useMoreThan: true, - )} ${baseToken.symbol}", - style: const TextStyle(fontWeight: FontWeight.w500)), + "${baseTokenAmount.maybeFormatCompactCurrency(isUSD: false, useLessThan: false, useMoreThan: true)} ${baseToken.symbol}", + style: const TextStyle(fontWeight: FontWeight.w500), + ), ], ), const SizedBox(height: 15), @@ -542,12 +506,9 @@ class _PreviewDepositModalState extends State with V3PoolCo Text(quoteToken.symbol), const Spacer(), Text( - "${quoteTokenAmount.maybeFormatCompactCurrency( - isUSD: false, - useLessThan: false, - useMoreThan: true, - )} ${quoteToken.symbol}", - style: const TextStyle(fontWeight: FontWeight.w500)), + "${quoteTokenAmount.maybeFormatCompactCurrency(isUSD: false, useLessThan: false, useMoreThan: true)} ${quoteToken.symbol}", + style: const TextStyle(fontWeight: FontWeight.w500), + ), ], ), const SizedBox(height: 10), @@ -572,12 +533,13 @@ class _PreviewDepositModalState extends State with V3PoolCo ), const SizedBox(width: 40), Flexible( - fit: FlexFit.loose, - child: _fieldColumn( - title: S.of(context).previewDepositModalNetwork, - image: SizedBox(width: 30, height: 30, child: widget.currentYield.network.icon), - value: widget.currentYield.network.label, - )), + fit: FlexFit.loose, + child: _fieldColumn( + title: S.of(context).previewDepositModalNetwork, + image: SizedBox(width: 30, height: 30, child: widget.currentYield.network.icon), + value: widget.currentYield.network.label, + ), + ), const SizedBox(width: 40), ], ), @@ -586,7 +548,7 @@ class _PreviewDepositModalState extends State with V3PoolCo spacing: 0, title: "${S.of(context).previewDepositModalYearlyYield} (${widget.yieldTimeFrame.label(context)})", - value: yieldTimeframed.formatPercent, + value: widget.currentYield.yieldTimeframed(widget.yieldTimeFrame).formatPercent, ), const SizedBox(height: 10), const ZupDivider(), @@ -612,7 +574,7 @@ class _PreviewDepositModalState extends State with V3PoolCo isLoading: depositButtonState.isLoading ?? false, width: double.maxFinite, ), - const SizedBox(height: 20) + const SizedBox(height: 20), ], ), ), @@ -624,61 +586,52 @@ class _PreviewDepositModalState extends State with V3PoolCo } Widget rangeInfoCard({required bool isMinPrice}) => Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: ZupColors.brand.withValues(alpha: 0.02), - border: Border.all(color: ZupColors.brand5, width: 0.5), - borderRadius: const BorderRadius.all(Radius.circular(12)), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ZupColors.brand.withValues(alpha: 0.02), + border: Border.all(color: ZupColors.brand5, width: 0.5), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isMinPrice ? S.of(context).previewDepositModalMinPrice : S.of(context).previewDepositModalMaxPrice, + style: const TextStyle(color: ZupColors.gray, fontWeight: FontWeight.w500), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - isMinPrice ? S.of(context).previewDepositModalMinPrice : S.of(context).previewDepositModalMaxPrice, - style: const TextStyle( - color: ZupColors.gray, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 5), - Text( - () { - if (isMinPrice) { - return widget.minPrice.isInfinity - ? "0" - : minPrice.maybeFormatCompactCurrency( - isUSD: false, - maxBeforeCompact: pow(10, 6), - useLessThan: true, - useMoreThan: true, - ); - } - - return widget.maxPrice.isInfinity - ? "∞" - : maxPrice.maybeFormatCompactCurrency( - isUSD: false, - maxBeforeCompact: pow(10, 6), - useLessThan: true, - useMoreThan: true, - ); - }.call(), - maxLines: 1, - overflow: TextOverflow.clip, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 18, - color: ZupColors.black, - )), - const SizedBox(height: 5), - Text( - "${baseToken.symbol} / ${quoteToken.symbol}", - style: const TextStyle( - color: ZupColors.gray, - fontWeight: FontWeight.w500, - ), - ), - ], + const SizedBox(height: 5), + Text( + () { + if (isMinPrice) { + return widget.minPrice.isInfinity + ? "0" + : minPrice.maybeFormatCompactCurrency( + isUSD: false, + maxBeforeCompact: pow(10, 6), + useLessThan: true, + useMoreThan: true, + ); + } + + return widget.maxPrice.isInfinity + ? "∞" + : maxPrice.maybeFormatCompactCurrency( + isUSD: false, + maxBeforeCompact: pow(10, 6), + useLessThan: true, + useMoreThan: true, + ); + }.call(), + maxLines: 1, + overflow: TextOverflow.clip, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 18, color: ZupColors.black), ), - ); + const SizedBox(height: 5), + Text( + "${baseToken.symbol} / ${quoteToken.symbol}", + style: const TextStyle(color: ZupColors.gray, fontWeight: FontWeight.w500), + ), + ], + ), + ); } diff --git a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart b/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart index 96e2b8a..1b9b5e4 100644 --- a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart +++ b/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:web3kit/web3kit.dart'; diff --git a/lib/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart b/lib/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart index b79e67a..adf95af 100644 --- a/lib/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart +++ b/lib/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart @@ -88,7 +88,7 @@ class _CreatePageSettingsDropdownState extends State children: [ sectionTitle(S.of(context).createPageSettingsDropdownMinimumLiquidity), const SizedBox(width: 8), - ZupTooltip( + ZupTooltip.text( key: const Key("min-liquidity-tooltip"), message: S.of(context).createPageSettingsDropdownMinimumLiquidityExplanation, child: Assets.icons.infoCircle.svg( @@ -170,7 +170,7 @@ class _CreatePageSettingsDropdownState extends State children: [ sectionTitle(S.of(context).createPageSettingsDropdownAllowedPoolTypes), const SizedBox(width: 8), - ZupTooltip( + ZupTooltip.text( key: const Key("pool-types-allowed-tooltip"), message: S.of(context).createPageSettingsDropdownAllowedPoolTypesDescription, child: Assets.icons.infoCircle.svg( diff --git a/lib/core/dtos/deposit_settings_dto.dart b/lib/core/dtos/deposit_settings_dto.dart index cdefd60..50b0d9c 100644 --- a/lib/core/dtos/deposit_settings_dto.dart +++ b/lib/core/dtos/deposit_settings_dto.dart @@ -5,14 +5,14 @@ part 'deposit_settings_dto.freezed.dart'; part 'deposit_settings_dto.g.dart'; @freezed -class DepositSettingsDto with _$DepositSettingsDto { +sealed class DepositSettingsDto with _$DepositSettingsDto { const DepositSettingsDto._(); static const defaultMaxSlippage = 0.5; static const defaultDeadlineMinutes = 10; @JsonSerializable(explicitToJson: true) - factory DepositSettingsDto({ + const factory DepositSettingsDto({ @Default(DepositSettingsDto.defaultMaxSlippage) @JsonKey(name: 'max_slippage') double maxSlippage, @Default(DepositSettingsDto.defaultDeadlineMinutes) @JsonKey(name: 'deadline_minutes') int deadlineMinutes, }) = _DepositSettingsDto; @@ -22,8 +22,5 @@ class DepositSettingsDto with _$DepositSettingsDto { factory DepositSettingsDto.fromJson(Map json) => _$DepositSettingsDtoFromJson(json); - factory DepositSettingsDto.fixture() => DepositSettingsDto( - maxSlippage: 122, - deadlineMinutes: 40, - ); + factory DepositSettingsDto.fixture() => const DepositSettingsDto(maxSlippage: 122, deadlineMinutes: 40); } diff --git a/lib/core/dtos/pool_search_filters_dto.dart b/lib/core/dtos/pool_search_filters_dto.dart index 4ce4f3d..4c589d1 100644 --- a/lib/core/dtos/pool_search_filters_dto.dart +++ b/lib/core/dtos/pool_search_filters_dto.dart @@ -4,7 +4,7 @@ part 'pool_search_filters_dto.freezed.dart'; part 'pool_search_filters_dto.g.dart'; @freezed -class PoolSearchFiltersDto with _$PoolSearchFiltersDto { +sealed class PoolSearchFiltersDto with _$PoolSearchFiltersDto { @JsonSerializable(explicitToJson: true) const factory PoolSearchFiltersDto({ @Default(0) num minTvlUsd, diff --git a/lib/core/dtos/pool_search_settings_dto.dart b/lib/core/dtos/pool_search_settings_dto.dart index 9565b98..bfabbc6 100644 --- a/lib/core/dtos/pool_search_settings_dto.dart +++ b/lib/core/dtos/pool_search_settings_dto.dart @@ -4,7 +4,7 @@ part 'pool_search_settings_dto.freezed.dart'; part 'pool_search_settings_dto.g.dart'; @freezed -class PoolSearchSettingsDto with _$PoolSearchSettingsDto { +sealed class PoolSearchSettingsDto with _$PoolSearchSettingsDto { static const num defaultMinLiquidityUSD = 1000; @JsonSerializable(explicitToJson: true) diff --git a/lib/core/dtos/position_dto.dart b/lib/core/dtos/position_dto.dart index edfe577..fb9e012 100644 --- a/lib/core/dtos/position_dto.dart +++ b/lib/core/dtos/position_dto.dart @@ -8,7 +8,7 @@ part 'position_dto.freezed.dart'; part 'position_dto.g.dart'; @freezed -class PositionDto with _$PositionDto { +sealed class PositionDto with _$PositionDto { const PositionDto._(); @JsonSerializable(explicitToJson: true) diff --git a/lib/core/dtos/protocol_dto.dart b/lib/core/dtos/protocol_dto.dart index 3c23722..bb4461a 100644 --- a/lib/core/dtos/protocol_dto.dart +++ b/lib/core/dtos/protocol_dto.dart @@ -7,7 +7,7 @@ part 'protocol_dto.g.dart'; String _readRawProtocolId(Map map, String key) => map['id']; @freezed -class ProtocolDto with _$ProtocolDto { +sealed class ProtocolDto with _$ProtocolDto { const ProtocolDto._(); @JsonSerializable(explicitToJson: true) diff --git a/lib/core/dtos/token_dto.dart b/lib/core/dtos/token_dto.dart index 2c20e2a..7cbf435 100644 --- a/lib/core/dtos/token_dto.dart +++ b/lib/core/dtos/token_dto.dart @@ -5,7 +5,7 @@ part 'token_dto.freezed.dart'; part 'token_dto.g.dart'; @freezed -class TokenDto with _$TokenDto { +sealed class TokenDto with _$TokenDto { @JsonSerializable(explicitToJson: true) const factory TokenDto({ @JsonKey(name: "id") String? internalId, diff --git a/lib/core/dtos/token_group_dto.dart b/lib/core/dtos/token_group_dto.dart new file mode 100644 index 0000000..689e235 --- /dev/null +++ b/lib/core/dtos/token_group_dto.dart @@ -0,0 +1,25 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/enums/app_environment.dart'; + +part 'token_group_dto.freezed.dart'; +part 'token_group_dto.g.dart'; + +@freezed +sealed class TokenGroupDto with _$TokenGroupDto { + const TokenGroupDto._(); + + @JsonSerializable(explicitToJson: true) + const factory TokenGroupDto({ + @Default("") String id, + @Default("") String name, + @Default([]) List tokens, + }) = _TokenGroupDto; + + String get logoUrl => "${AppEnvironment.current.apiUrl}/static/group-icons/$id.png"; + + factory TokenGroupDto.fromJson(Map json) => _$TokenGroupDtoFromJson(json); + + factory TokenGroupDto.fixture() => + TokenGroupDto(id: "usd-stablecoins-group", name: "USD Stablecoins", tokens: [TokenDto.fixture()]); +} diff --git a/lib/core/dtos/token_list_dto.dart b/lib/core/dtos/token_list_dto.dart new file mode 100644 index 0000000..7d89bab --- /dev/null +++ b/lib/core/dtos/token_list_dto.dart @@ -0,0 +1,26 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; + +part 'token_list_dto.freezed.dart'; +part 'token_list_dto.g.dart'; + +@freezed +sealed class TokenListDto with _$TokenListDto { + @JsonSerializable(explicitToJson: true) + const factory TokenListDto({ + @Default([]) List tokenGroups, + @Default([]) List popularTokens, + }) = _TokenListDto; + + factory TokenListDto.fromJson(Map json) => _$TokenListDtoFromJson(json); + + factory TokenListDto.fixture() => TokenListDto( + popularTokens: [ + TokenDto.fixture(), + ], + tokenGroups: [ + TokenGroupDto.fixture(), + ], + ); +} diff --git a/lib/core/dtos/token_price_dto.dart b/lib/core/dtos/token_price_dto.dart index 920427c..c98f978 100644 --- a/lib/core/dtos/token_price_dto.dart +++ b/lib/core/dtos/token_price_dto.dart @@ -4,7 +4,7 @@ part 'token_price_dto.freezed.dart'; part 'token_price_dto.g.dart'; @freezed -class TokenPriceDto with _$TokenPriceDto { +sealed class TokenPriceDto with _$TokenPriceDto { @JsonSerializable(explicitToJson: true) factory TokenPriceDto({ @Default(0) num usdPrice, diff --git a/lib/core/dtos/yield_dto.dart b/lib/core/dtos/yield_dto.dart index fb3ef57..6b149aa 100644 --- a/lib/core/dtos/yield_dto.dart +++ b/lib/core/dtos/yield_dto.dart @@ -1,39 +1,16 @@ -import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:web3kit/core/ethereum_constants.dart'; import 'package:zup_app/core/dtos/protocol_dto.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/enums/pool_type.dart'; -import 'package:zup_app/l10n/gen/app_localizations.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; part 'yield_dto.freezed.dart'; part 'yield_dto.g.dart'; -enum YieldTimeFrame { day, month, threeMonth, unknown } - -extension YieldTimeFrameExtension on YieldTimeFrame { - bool get isDay => this == YieldTimeFrame.day; - bool get isMonth => this == YieldTimeFrame.month; - bool get isThreeMonth => this == YieldTimeFrame.threeMonth; - - String label(BuildContext context) => [ - S.of(context).twentyFourHours, - S.of(context).month, - S.of(context).threeMonths, - "???", - ][index]; - - String compactDaysLabel(BuildContext context) => [ - S.of(context).twentyFourHoursCompact, - S.of(context).monthCompact, - S.of(context).threeMonthsCompact, - "???", - ][index]; -} - @freezed -class YieldDto with _$YieldDto { +sealed class YieldDto with _$YieldDto { const YieldDto._(); @JsonSerializable(explicitToJson: true) const factory YieldDto({ @@ -45,6 +22,7 @@ class YieldDto with _$YieldDto { required ProtocolDto protocol, required int feeTier, required num yield24h, + required num yield7d, required num yield30d, required num yield90d, required int chainId, @@ -65,41 +43,47 @@ class YieldDto with _$YieldDto { int get token0NetworkDecimals => token0.decimals[network.chainId]!; int get token1NetworkDecimals => token1.decimals[network.chainId]!; + num yieldTimeframed(YieldTimeFrame yieldTimeFrame) { + switch (yieldTimeFrame) { + case YieldTimeFrame.day: + return yield24h; + case YieldTimeFrame.week: + return yield7d; + case YieldTimeFrame.month: + return yield30d; + case YieldTimeFrame.threeMonth: + return yield90d; + } + } + factory YieldDto.fromJson(Map json) => _$YieldDtoFromJson(json); factory YieldDto.fixture() => YieldDto( - feeTier: 0, - yield24h: 32.2, - yield30d: 32.2, - yield90d: 32.2, - latestTick: "1567241", - positionManagerAddress: "0x5Df2f0aFb5b5bB2Df9D1e9C7b6f5f0DD5f9eD5e0", - poolAddress: "0x5Df2f0aFb5b5bB2Df9D1e9C7b6f5f0DD5f9eD5e0", - poolType: PoolType.v3, - token0: TokenDto.fixture().copyWith( - symbol: "USDC", - decimals: { - AppNetworks.sepolia.chainId: 6, - }, - addresses: { - AppNetworks.sepolia.chainId: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - }, - logoUrl: - "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", - ), - token1: TokenDto.fixture().copyWith( - symbol: "WETH", - decimals: { - AppNetworks.sepolia.chainId: 18, - }, - addresses: { - AppNetworks.sepolia.chainId: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - }, - logoUrl: - "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - ), - tickSpacing: 10, - protocol: ProtocolDto.fixture(), - chainId: AppNetworks.sepolia.chainId, - ); + feeTier: 0, + yield24h: 32.2, + yield30d: 32.2, + yield90d: 32.2, + yield7d: 12, + latestTick: "1567241", + positionManagerAddress: "0x5Df2f0aFb5b5bB2Df9D1e9C7b6f5f0DD5f9eD5e0", + poolAddress: "0x5Df2f0aFb5b5bB2Df9D1e9C7b6f5f0DD5f9eD5e0", + poolType: PoolType.v3, + token0: TokenDto.fixture().copyWith( + symbol: "USDC", + decimals: {AppNetworks.sepolia.chainId: 6}, + addresses: {AppNetworks.sepolia.chainId: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}, + logoUrl: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + ), + token1: TokenDto.fixture().copyWith( + symbol: "WETH", + decimals: {AppNetworks.sepolia.chainId: 18}, + addresses: {AppNetworks.sepolia.chainId: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}, + logoUrl: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + ), + tickSpacing: 10, + protocol: ProtocolDto.fixture(), + chainId: AppNetworks.sepolia.chainId, + ); } diff --git a/lib/core/dtos/yields_dto.dart b/lib/core/dtos/yields_dto.dart index ac83d41..c77a8bd 100644 --- a/lib/core/dtos/yields_dto.dart +++ b/lib/core/dtos/yields_dto.dart @@ -5,12 +5,13 @@ import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/pool_type.dart'; import 'package:zup_app/core/enums/protocol_id.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; part 'yields_dto.freezed.dart'; part 'yields_dto.g.dart'; @freezed -class YieldsDto with _$YieldsDto { +sealed class YieldsDto with _$YieldsDto { const YieldsDto._(); @JsonSerializable(explicitToJson: true) @@ -21,55 +22,78 @@ class YieldsDto with _$YieldsDto { bool get isEmpty => pools.isEmpty; - YieldDto get best24hYield => ([...pools]..sort((a, b) => b.yield24h.compareTo(a.yield24h))).first; - YieldDto get best30dYield => ([...pools]..sort((a, b) => b.yield30d.compareTo(a.yield30d))).first; - YieldDto get best90dYield => ([...pools]..sort((a, b) => b.yield90d.compareTo(a.yield90d))).first; + List get poolsSortedBy24hYield { + return [...pools]..sort((a, b) => b.yield24h.compareTo(a.yield24h)); + } + + List get poolsSortedBy7dYield { + return [...pools]..sort((a, b) => b.yield7d.compareTo(a.yield7d)); + } + + List get poolsSortedBy30dYield { + return [...pools]..sort((a, b) => b.yield30d.compareTo(a.yield30d)); + } + + List get poolsSortedBy90dYield { + return [...pools]..sort((a, b) => b.yield90d.compareTo(a.yield90d)); + } + + List poolsSortedByTimeframe(YieldTimeFrame timeframe) { + switch (timeframe) { + case YieldTimeFrame.day: + return poolsSortedBy24hYield; + case YieldTimeFrame.week: + return poolsSortedBy7dYield; + case YieldTimeFrame.month: + return poolsSortedBy30dYield; + case YieldTimeFrame.threeMonth: + return poolsSortedBy90dYield; + } + } factory YieldsDto.fromJson(Map json) => _$YieldsDtoFromJson(json); - factory YieldsDto.empty() => const YieldsDto( - pools: [], - ); + factory YieldsDto.empty() => const YieldsDto(pools: []); factory YieldsDto.fixture() => YieldsDto( - filters: PoolSearchFiltersDto.fixture(), - pools: [ - YieldDto( - latestTick: "637812562", - positionManagerAddress: "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4", - poolType: PoolType.v3, - token0: const TokenDto( - addresses: {11155111: "0x02a3e7E0480B668bD46b42852C58363F93e3bA5C"}, - decimals: {11155111: 6}, - logoUrl: - "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4/logo.png", - name: "USDC", - symbol: "USDC", - ), - token1: const TokenDto( - addresses: {11155111: "0x5300000000000000000000000000000000000004"}, - decimals: {11155111: 18}, - logoUrl: - "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x5300000000000000000000000000000000000004/logo.png", - name: "Wrapped Ether", - symbol: "WETH", - ), - chainId: 11155111, - poolAddress: "0x4040CE732c1A538A4Ac3157FDC35179D73ea76cd", - tickSpacing: 10, - yield24h: 1732.42, - yield30d: 765.61, - yield90d: 2022.99, - totalValueLockedUSD: 65434567890.21, - feeTier: 500, - protocol: ProtocolDto( - id: ProtocolId.pancakeSwapInfinityCL, - rawId: ProtocolId.pancakeSwapInfinityCL.toRawJsonValue, - name: "PancakeSwap", - logo: - "https://raw.githubusercontent.com/trustwallet/assets/master/dapps/exchange.pancakeswap.finance.png", - ), - ), - ], - ); + filters: PoolSearchFiltersDto.fixture(), + pools: [ + YieldDto( + latestTick: "637812562", + positionManagerAddress: "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4", + poolType: PoolType.v3, + yield7d: 21, + token0: const TokenDto( + addresses: {11155111: "0x02a3e7E0480B668bD46b42852C58363F93e3bA5C"}, + decimals: {11155111: 6}, + logoUrl: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4/logo.png", + name: "USDC", + symbol: "USDC", + ), + token1: const TokenDto( + addresses: {11155111: "0x5300000000000000000000000000000000000004"}, + decimals: {11155111: 18}, + logoUrl: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x5300000000000000000000000000000000000004/logo.png", + name: "Wrapped Ether", + symbol: "WETH", + ), + chainId: 11155111, + poolAddress: "0x4040CE732c1A538A4Ac3157FDC35179D73ea76cd", + tickSpacing: 10, + yield24h: 1732.42, + yield30d: 765.61, + yield90d: 2022.99, + totalValueLockedUSD: 65434567890.21, + feeTier: 500, + protocol: ProtocolDto( + id: ProtocolId.pancakeSwapInfinityCL, + rawId: ProtocolId.pancakeSwapInfinityCL.toRawJsonValue, + name: "PancakeSwap", + logo: "https://raw.githubusercontent.com/trustwallet/assets/master/dapps/exchange.pancakeswap.finance.png", + ), + ), + ], + ); } diff --git a/lib/core/enums/app_environment.dart b/lib/core/enums/app_environment.dart index a8a6416..01dfd92 100644 --- a/lib/core/enums/app_environment.dart +++ b/lib/core/enums/app_environment.dart @@ -1,37 +1,47 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; import 'package:zup_core/zup_core.dart'; enum AppEnvironment { prod, local, + test, stage; static AppEnvironment get current { const env = String.fromEnvironment("env"); if (env.equals(AppEnvironment.stage.value)) { - return AppEnvironment.stage; + return stage; } if (env.equals(AppEnvironment.prod.value)) { - return AppEnvironment.prod; + return prod; } if (env.equals(AppEnvironment.local.value)) { - return AppEnvironment.local; + return local; + } + + if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) { + return test; } throw UnsupportedError("Environment either not supported or not set"); } String get value => switch (this) { - prod => "prod", - stage => "stage", - local => "local", - }; + prod => "prod", + stage => "stage", + local => "local", + test => "test", + }; String get apiUrl => switch (this) { - prod => "https://api.zupprotocol.xyz", - stage => "https://staging.api.zupprotocol.xyz", - local => "http://localhost:3000", - }; + prod => "https://api.zupprotocol.xyz", + stage => "https://staging.api.zupprotocol.xyz", + local => "http://localhost:3000", + test => "http://test-env", + }; } diff --git a/lib/core/enums/yield_timeframe.dart b/lib/core/enums/yield_timeframe.dart new file mode 100644 index 0000000..b5cfe50 --- /dev/null +++ b/lib/core/enums/yield_timeframe.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:zup_app/l10n/gen/app_localizations.dart'; + +enum YieldTimeFrame { day, week, month, threeMonth } + +extension YieldTimeFrameExtension on YieldTimeFrame { + bool get isDay => this == YieldTimeFrame.day; + bool get isWeek => this == YieldTimeFrame.week; + bool get isMonth => this == YieldTimeFrame.month; + bool get isThreeMonth => this == YieldTimeFrame.threeMonth; + + String label(BuildContext context) { + return switch (this) { + YieldTimeFrame.day => S.of(context).twentyFourHours, + YieldTimeFrame.week => S.of(context).week, + YieldTimeFrame.month => S.of(context).month, + YieldTimeFrame.threeMonth => S.of(context).threeMonths, + }; + } + + String compactDaysLabel(BuildContext context) => switch (this) { + YieldTimeFrame.day => S.of(context).twentyFourHoursCompact, + YieldTimeFrame.week => S.of(context).weekCompact, + YieldTimeFrame.month => S.of(context).monthCompact, + YieldTimeFrame.threeMonth => S.of(context).threeMonthsCompact, + }; +} diff --git a/lib/core/enums/zup_navigator_paths.dart b/lib/core/enums/zup_navigator_paths.dart index be1107b..dc8adec 100644 --- a/lib/core/enums/zup_navigator_paths.dart +++ b/lib/core/enums/zup_navigator_paths.dart @@ -1,3 +1,4 @@ +import 'package:zup_app/core/zup_route_params_names.dart'; import 'package:zup_app/zup_app.dart'; enum ZupNavigatorPaths { @@ -6,14 +7,18 @@ enum ZupNavigatorPaths { deposit; String get path => switch (this) { - initial => routePaths.create.path, - newPosition => routePaths.create.path, - deposit => routePaths.create.deposit, - }; + initial => routePaths.create.path, + newPosition => routePaths.create.path, + deposit => routePaths.create.deposit, + }; - ({String param0, String param1, String param3})? get routeParamsName => switch (this) { - initial => null, - newPosition => null, - deposit => (param0: "token0", param1: "token1", param3: "network"), - }; + T routeParamsNames() { + final params = switch (this) { + initial => ZupInitialRouteParamsNames(), + newPosition => ZupNewPositionRouteParamsNames(), + deposit => ZupDepositRouteParamsNames(), + }; + + return params as T; + } } diff --git a/lib/core/injections.dart b/lib/core/injections.dart index c4a2e38..68af3bf 100644 --- a/lib/core/injections.dart +++ b/lib/core/injections.dart @@ -39,21 +39,22 @@ abstract class InjectInstanceNames { static final lottieClick = Assets.lotties.click.path; static final lottieEmpty = Assets.lotties.empty.path; static final lottieRadar = Assets.lotties.radar.path; + static final lottieNumbers = Assets.lotties.numbers.path; static final lottieMatching = Assets.lotties.matching.path; static final lottieSearching = Assets.lotties.seaching.path; static const zupAPIDio = 'zup_api_dio'; static const confettiController10s = 'confetti_controller_10s'; static const zupHolderFactory = 'zup_holder_factory'; + static const infinityAnimationAutoPlay = 'infinity_animation_auto_play'; } Future setupInjections() async { await inject.reset(); inject.registerLazySingleton( - () => Dio(BaseOptions(baseUrl: AppEnvironment.current.apiUrl)) - ..interceptors.add( - LogInterceptor(request: true, requestBody: true, responseBody: true, error: true), - ), + () => + Dio(BaseOptions(baseUrl: AppEnvironment.current.apiUrl)) + ..interceptors.add(LogInterceptor(request: true, requestBody: true, responseBody: true, error: true)), instanceName: InjectInstanceNames.zupAPIDio, ); @@ -68,8 +69,10 @@ Future setupInjections() async { inject.registerLazySingleton(() => AppCubit(inject(), inject())); inject.registerLazySingleton(() => PositionsRepository()); inject.registerLazySingleton(() => ZupCachedImage()); + inject.registerLazySingleton(() => true, instanceName: InjectInstanceNames.infinityAnimationAutoPlay); inject.registerLazySingleton( - () => TokensRepository(inject(instanceName: InjectInstanceNames.zupAPIDio))); + () => TokensRepository(inject(instanceName: InjectInstanceNames.zupAPIDio)), + ); inject.registerLazySingleton( () => TokenSelectorModalCubit(inject(), inject(), inject()), ); @@ -79,10 +82,7 @@ Future setupInjections() async { ); inject.registerLazySingleton(() => FirebaseAnalytics.instance); inject.registerLazySingleton( - () => ZupAnalytics( - inject(), - inject(), - ), + () => ZupAnalytics(inject(), inject()), ); inject.registerLazySingleton(() => ZupHolder()); inject.registerFactory(() => ZupHolder(), instanceName: InjectInstanceNames.zupHolderFactory); @@ -108,6 +108,10 @@ Future setupInjections() async { () => Assets.lotties.radar.lottie(), instanceName: InjectInstanceNames.lottieRadar, ); + inject.registerLazySingleton( + () => Assets.lotties.numbers.lottie(), + instanceName: InjectInstanceNames.lottieNumbers, + ); inject.registerLazySingleton( () => Assets.lotties.matching.lottie(), instanceName: InjectInstanceNames.lottieMatching, @@ -116,28 +120,26 @@ Future setupInjections() async { () => Assets.lotties.seaching.lottie(), instanceName: InjectInstanceNames.lottieSearching, ); - inject.registerLazySingleton( - () => UniswapV4StateView(), - ); + inject.registerLazySingleton(() => UniswapV4StateView()); inject.registerLazySingleton(() => EthereumAbiCoder()); - inject.registerLazySingleton( - () => PancakeSwapInfinityClPoolManager(), - ); + inject.registerLazySingleton(() => PancakeSwapInfinityClPoolManager()); inject.registerLazySingleton( - () => PoolService(inject(), inject(), inject(), - inject(), inject(), inject()), + () => PoolService( + inject(), + inject(), + inject(), + inject(), + inject(), + inject(), + ), ); - inject.registerLazySingleton( - () => UniswapPermit2(), - ); + inject.registerLazySingleton(() => UniswapPermit2()); - inject.registerLazySingleton( - () => UniswapV4PositionManager(), - ); + inject.registerLazySingleton(() => UniswapV4PositionManager()); // WARNING: this should always be factory following the instructions inject.registerFactory( diff --git a/lib/core/repositories/tokens_repository.dart b/lib/core/repositories/tokens_repository.dart index 455af32..aa94d1a 100644 --- a/lib/core/repositories/tokens_repository.dart +++ b/lib/core/repositories/tokens_repository.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_list_dto.dart'; import 'package:zup_app/core/dtos/token_price_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; @@ -10,15 +11,15 @@ class TokensRepository { bool isSearchingTokens = false; CancelToken? _searchTokenLastCancelToken; - Future> getPopularTokens(AppNetworks network) async { + Future getTokenList(AppNetworks network) async { final request = await _zupAPIDio.get( - "/tokens/popular", + "/tokens/list", queryParameters: { if (!network.isAllNetworks) "chainId": int.parse(network.chainInfo.hexChainId), }, ); - return request.data.map((token) => TokenDto.fromJson(token)).toList(); + return TokenListDto.fromJson(request.data); } Future> searchToken(String query, AppNetworks network) async { diff --git a/lib/core/repositories/yield_repository.dart b/lib/core/repositories/yield_repository.dart index 0f2ac4b..1935bcf 100644 --- a/lib/core/repositories/yield_repository.dart +++ b/lib/core/repositories/yield_repository.dart @@ -9,15 +9,19 @@ class YieldRepository { final Dio _zupAPIDio; Future getSingleNetworkYield({ - required String token0Address, - required String token1Address, + required String? token0Address, + required String? token1Address, + required String? group0Id, + required String? group1Id, required AppNetworks network, required PoolSearchSettingsDto searchSettings, required List blockedProtocolIds, }) async { final response = await _zupAPIDio.post("/pools/search/${network.chainId}", queryParameters: { - "token0Address": token0Address, - "token1Address": token1Address, + if (token0Address != null) "token0Address": token0Address, + if (token1Address != null) "token1Address": token1Address, + if (group0Id != null) "group0Id": group0Id, + if (group1Id != null) "group1Id": group1Id, }, data: { "filters": { "minTvlUsd": searchSettings.minLiquidityUSD, @@ -33,15 +37,19 @@ class YieldRepository { } Future getAllNetworksYield({ - required String token0InternalId, - required String token1InternalId, + required String? token0InternalId, + required String? token1InternalId, + required String? group0Id, + required String? group1Id, required PoolSearchSettingsDto searchSettings, required List blockedProtocolIds, bool testnetMode = false, }) async { final response = await _zupAPIDio.post("/pools/search/all", queryParameters: { - "token0Id": token0InternalId, - "token1Id": token1InternalId, + if (token0InternalId != null) "token0Id": token0InternalId, + if (token1InternalId != null) "token1Id": token1InternalId, + if (group0Id != null) "group0Id": group0Id, + if (group1Id != null) "group1Id": group1Id, }, data: { "filters": { "minTvlUsd": searchSettings.minLiquidityUSD, diff --git a/lib/core/zup_analytics.dart b/lib/core/zup_analytics.dart index ace5a62..2d56766 100644 --- a/lib/core/zup_analytics.dart +++ b/lib/core/zup_analytics.dart @@ -68,15 +68,19 @@ class ZupAnalytics { } Future logSearch({ - required String token0, - required String token1, + required String? token0, + required String? token1, + required String? group0, + required String? group1, required String network, }) async { await _log( "user_searched_yields", { - "token0_address": "hex:$token0", - "token1_address": "hex:$token1", + "token0": "id:$token0", + "token1": "id:$token1", + "group0": "id:$group0", + "group1": "id:$group1", "network": network, }, ); diff --git a/lib/core/zup_navigator.dart b/lib/core/zup_navigator.dart index ec2b1b2..1eef5c3 100644 --- a/lib/core/zup_navigator.dart +++ b/lib/core/zup_navigator.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:routefly/routefly.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/enums/zup_navigator_paths.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; class ZupNavigator { Listenable get listenable => Routefly.listenable; @@ -13,11 +14,24 @@ class ZupNavigator { Future navigateToNewPosition() async => await Routefly.navigate(ZupNavigatorPaths.newPosition.path); - Future navigateToDeposit(String token0, String token1, AppNetworks network) async { + Future navigateToDeposit({ + required String? token0, + required String? token1, + required String? group0, + required String? group1, + required AppNetworks network, + }) async { const depositPath = ZupNavigatorPaths.deposit; + final depositPathParams = depositPath.routeParamsNames(); + + final token0UrlParam = token0 != null ? "${depositPathParams.token0}=$token0" : ""; + final token1UrlParam = token1 != null ? "${depositPathParams.token1}=$token1" : ""; + final group0UrlParam = group0 != null ? "${depositPathParams.group0}=$group0" : ""; + final group1UrlParam = group1 != null ? "${depositPathParams.group1}=$group1" : ""; + final networkUrlParam = "${depositPathParams.network}=${network.name}"; await Routefly.pushNavigate( - "${depositPath.path}?${depositPath.routeParamsName!.param0}=$token0&${depositPath.routeParamsName!.param1}=$token1&${depositPath.routeParamsName!.param3}=${network.name}", + "${depositPath.path}?$token0UrlParam&$token1UrlParam&$group0UrlParam&$group1UrlParam&$networkUrlParam", ); } diff --git a/lib/core/zup_route_params_names.dart b/lib/core/zup_route_params_names.dart new file mode 100644 index 0000000..7c1c8b0 --- /dev/null +++ b/lib/core/zup_route_params_names.dart @@ -0,0 +1,17 @@ +abstract class ZupRouteParamsNames {} + +class ZupDepositRouteParamsNames extends ZupRouteParamsNames { + final String token0 = "token0"; + final String token1 = "token1"; + final String group0 = "group0"; + final String group1 = "group1"; + final String network = "network"; +} + +class ZupInitialRouteParamsNames extends ZupRouteParamsNames { + ZupInitialRouteParamsNames(); +} + +class ZupNewPositionRouteParamsNames extends ZupRouteParamsNames { + ZupNewPositionRouteParamsNames(); +} diff --git a/lib/l10n/en.arb b/lib/l10n/en.arb index c5e8609..97c51fc 100644 --- a/lib/l10n/en.arb +++ b/lib/l10n/en.arb @@ -9,6 +9,8 @@ } }, "twentyFourHours": "24h", + "week": "Week", + "weekCompact": "7d", "appFooterTermsOfUse": "Terms of Use", "privacyPolicy": "Privacy Policy", "appFooterContactUs": "Contact Us", @@ -152,7 +154,15 @@ } } }, - "yieldCardYieldYearly": "Yield (Yearly)", + "yieldCardYearlyYield": "Yearly Yield", + "yieldCardVisitProtocol": "Visit {protocolName}", + "@yieldCardVisitProtocol": { + "placeholders": { + "protocolName": { + "type": "String" + } + } + }, "yieldCardAverageYieldYearly": "Average Yield (Yearly)", "previewDepositModalDepositSuccessSnackBarHelperButtonTitle": "View Transaction", "previewDepositModalApproveSuccessSnackBarHelperButtonTitle": "View Transaction", @@ -186,10 +196,13 @@ "createPageSelectTokensStageSearchSettings": "Search Settings", "depositPageLoadingStep1Title": "Matching Tokens...", "depositPageLoadingStep1Description": "Pairing Token A and Token B to kick off the search for top yields!", - "depositPageLoadingStep2Title": "Scanning the Pools...", - "depositPageLoadingStep2Description": "Searching through the sea of pools for the best yields, hang tight!", - "depositPageLoadingStep3Title": "Fetching the Best yields for you...", - "depositPageLoadingStep3Description": "Got it! Just adding a touch of sparkle to your perfect match!", + "depositPageLoadingStep2Title": "Pair hunting…", + "depositPageLoadingStep2Description": "Searching through more than a thousand pool combos… so you don't have to", + "depositPageLoadingStep3Title": "Yield optimizer at work…", + "depositPageLoadingStep3Description": "Scanning pools, calculating returns, and filtering the noise", + "depositPageBestYieldsIn": "Best Yields in", + "depositPageLoadingStep4Title": "Fetching the Best yields for you...", + "depositPageLoadingStep4Description": "Got it! Just adding a touch of sparkle to your perfect match!", "depositPageErrorStateTitle": "Oops! Something went wrong!", "depositPageErrorStateDescription": "We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don’t hesitate to reach out to us!", "depositPageEmptyStateTitle": "No Pools Found", @@ -197,7 +210,7 @@ "depositPageEmptyStateHelpButtonTitle": "Go Back to New Position", "depositPageBackButtonTitle": "Select Pair", "depositPageTitle": "Add liquidity", - "depositPageTimeFrameTooltipMessage": "Select a time-frame that matches your goal with this pool: a quick win (Short term), a balanced approach (Medium term), or a long haul (Long term).", + "depositPageTimeFrameTooltipMessage": "Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.", "depositPageTimeFrameTooltipHelperButtonTitle": " Learn more", "depositPageTimeFrameTitle": "Preferred time frame", "depositPageNoYieldSelectedTitle": "Pick a yield to deposit", @@ -231,6 +244,8 @@ "depositPageDeposit": "Deposit", "connectYourWallet": "Connect your wallet", "tokenSelectorModalTitle": "Select a token", + "tokenSelectorModalTokenGroups": "Token Groups", + "tokenSelectorModalTokenGroupsTooltipMessage": "Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we'll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.", "tokenSelectorModalDescription": "Pick a token from our list or search by symbol or address to build your ideal liquidity pool!", "tokenSelectorModalSearchTitle": "Search token or paste address", "tokenSelectorModalSearchTitleAllNetworks": "Search token by symbol or name", diff --git a/lib/l10n/gen/app_localizations.dart b/lib/l10n/gen/app_localizations.dart index 69e8044..aaf2203 100644 --- a/lib/l10n/gen/app_localizations.dart +++ b/lib/l10n/gen/app_localizations.dart @@ -62,7 +62,7 @@ import 'app_localizations_en.dart'; /// property. abstract class S { S(String locale) - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; @@ -84,11 +84,11 @@ abstract class S { /// of delegates is preferred or required. static const List> localizationsDelegates = >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [Locale('en')]; @@ -105,6 +105,18 @@ abstract class S { /// **'24h'** String get twentyFourHours; + /// No description provided for @week. + /// + /// In en, this message translates to: + /// **'Week'** + String get week; + + /// No description provided for @weekCompact. + /// + /// In en, this message translates to: + /// **'7d'** + String get weekCompact; + /// No description provided for @appFooterTermsOfUse. /// /// In en, this message translates to: @@ -151,8 +163,9 @@ abstract class S { /// /// In en, this message translates to: /// **'Showing only liquidity pools with more than {minLiquidity}.'** - String depositPageShowingOnlyPoolsWithMoreThan( - {required String minLiquidity}); + String depositPageShowingOnlyPoolsWithMoreThan({ + required String minLiquidity, + }); /// No description provided for @depositPageShowingAllPools. /// @@ -170,8 +183,9 @@ abstract class S { /// /// In en, this message translates to: /// **'Search only for pools with more than {minLiquidity}?'** - String depositPageSearchOnlyForPoolsWithMorethan( - {required String minLiquidity}); + String depositPageSearchOnlyForPoolsWithMorethan({ + required String minLiquidity, + }); /// No description provided for @depositPageTrySearchAllPools. /// @@ -285,8 +299,10 @@ abstract class S { /// /// In en, this message translates to: /// **'Depositing into {baseTokenSymbol}/{quoteTokenSymbol} pool'** - String previewDepositModalDepositingIntoPool( - {required String baseTokenSymbol, required String quoteTokenSymbol}); + String previewDepositModalDepositingIntoPool({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + }); /// No description provided for @previewDepositModalApproveToken. /// @@ -340,8 +356,9 @@ abstract class S { /// /// In en, this message translates to: /// **'{tokenSymbol} approved successfully. '** - String previewDepositModalApproveSuccessSnackBarMessage( - {required String tokenSymbol}); + String previewDepositModalApproveSuccessSnackBarMessage({ + required String tokenSymbol, + }); /// No description provided for @previewDepositModalMyPosition. /// @@ -395,10 +412,11 @@ abstract class S { /// /// In en, this message translates to: /// **'Successfully Deposited into the {baseTokenSymbol}/{quoteTokenSymbol} Pool at {protocol}. '** - String previewDepositModalDepositSuccessSnackBarMessage( - {required String baseTokenSymbol, - required String quoteTokenSymbol, - required String protocol}); + String previewDepositModalDepositSuccessSnackBarMessage({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + required String protocol, + }); /// No description provided for @yieldCardTimeFrameBest. /// @@ -412,11 +430,17 @@ abstract class S { /// **'This pool is at {network}'** String yieldCardThisPoolIsAtNetwork({required String network}); - /// No description provided for @yieldCardYieldYearly. + /// No description provided for @yieldCardYearlyYield. /// /// In en, this message translates to: - /// **'Yield (Yearly)'** - String get yieldCardYieldYearly; + /// **'Yearly Yield'** + String get yieldCardYearlyYield; + + /// No description provided for @yieldCardVisitProtocol. + /// + /// In en, this message translates to: + /// **'Visit {protocolName}'** + String yieldCardVisitProtocol({required String protocolName}); /// No description provided for @yieldCardAverageYieldYearly. /// @@ -542,8 +566,9 @@ abstract class S { /// /// In en, this message translates to: /// **'Exchanges ({exchangesCount})'** - String exchangesFilterDropdownButtonTitleNumered( - {required String exchangesCount}); + String exchangesFilterDropdownButtonTitleNumered({ + required String exchangesCount, + }); /// No description provided for @createPageSelectTokensStageTokenA. /// @@ -578,27 +603,45 @@ abstract class S { /// No description provided for @depositPageLoadingStep2Title. /// /// In en, this message translates to: - /// **'Scanning the Pools...'** + /// **'Pair hunting…'** String get depositPageLoadingStep2Title; /// No description provided for @depositPageLoadingStep2Description. /// /// In en, this message translates to: - /// **'Searching through the sea of pools for the best yields, hang tight!'** + /// **'Searching through more than a thousand pool combos… so you don\'t have to'** String get depositPageLoadingStep2Description; /// No description provided for @depositPageLoadingStep3Title. /// /// In en, this message translates to: - /// **'Fetching the Best yields for you...'** + /// **'Yield optimizer at work…'** String get depositPageLoadingStep3Title; /// No description provided for @depositPageLoadingStep3Description. /// /// In en, this message translates to: - /// **'Got it! Just adding a touch of sparkle to your perfect match!'** + /// **'Scanning pools, calculating returns, and filtering the noise'** String get depositPageLoadingStep3Description; + /// No description provided for @depositPageBestYieldsIn. + /// + /// In en, this message translates to: + /// **'Best Yields in'** + String get depositPageBestYieldsIn; + + /// No description provided for @depositPageLoadingStep4Title. + /// + /// In en, this message translates to: + /// **'Fetching the Best yields for you...'** + String get depositPageLoadingStep4Title; + + /// No description provided for @depositPageLoadingStep4Description. + /// + /// In en, this message translates to: + /// **'Got it! Just adding a touch of sparkle to your perfect match!'** + String get depositPageLoadingStep4Description; + /// No description provided for @depositPageErrorStateTitle. /// /// In en, this message translates to: @@ -644,7 +687,7 @@ abstract class S { /// No description provided for @depositPageTimeFrameTooltipMessage. /// /// In en, this message translates to: - /// **'Select a time-frame that matches your goal with this pool: a quick win (Short term), a balanced approach (Medium term), or a long haul (Long term).'** + /// **'Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.'** String get depositPageTimeFrameTooltipMessage; /// No description provided for @depositPageTimeFrameTooltipHelperButtonTitle. @@ -719,6 +762,18 @@ abstract class S { /// **'Select a token'** String get tokenSelectorModalTitle; + /// No description provided for @tokenSelectorModalTokenGroups. + /// + /// In en, this message translates to: + /// **'Token Groups'** + String get tokenSelectorModalTokenGroups; + + /// No description provided for @tokenSelectorModalTokenGroupsTooltipMessage. + /// + /// In en, this message translates to: + /// **'Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we\'ll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.'** + String get tokenSelectorModalTokenGroupsTooltipMessage; + /// No description provided for @tokenSelectorModalDescription. /// /// In en, this message translates to: @@ -753,8 +808,9 @@ abstract class S { /// /// In en, this message translates to: /// **'We hit a snag while searching for a token matching {searchedTerm}. Give it another go, and if it keeps happening, feel free to reach us'** - String tokenSelectorModalSearchErrorDescription( - {required String searchedTerm}); + String tokenSelectorModalSearchErrorDescription({ + required String searchedTerm, + }); /// No description provided for @noResultsFor. /// @@ -862,17 +918,19 @@ abstract class S { /// /// In en, this message translates to: /// **'It looks like you don’t have any positions in {network} yet.\nGo ahead and create one to get started!'** - String positionsPageNoPositionsInNetworkDescription( - {required String network}); + String positionsPageNoPositionsInNetworkDescription({ + required String network, + }); /// No description provided for @positionCardTokenPerToken. /// /// In en, this message translates to: /// **'{token0Qtd} {token0Symbol} per {token1Symbol}'** - String positionCardTokenPerToken( - {required String token0Qtd, - required String token0Symbol, - required String token1Symbol}); + String positionCardTokenPerToken({ + required String token0Qtd, + required String token0Symbol, + required String token1Symbol, + }); /// No description provided for @positionsPageMyPositions. /// @@ -992,8 +1050,10 @@ abstract class S { /// /// In en, this message translates to: /// **'Depositing into {token0Symbol}/{token1Symbol} Pool...'** - String previewDepositModalCubitDepositingSnackBarMessage( - {required String token0Symbol, required String token1Symbol}); + String previewDepositModalCubitDepositingSnackBarMessage({ + required String token0Symbol, + required String token1Symbol, + }); /// No description provided for @previewDepositModalCubitApprovingSnackBarMessage. /// @@ -1005,8 +1065,9 @@ abstract class S { /// /// In en, this message translates to: /// **'Successfully approved {tokenSymbol}'** - String previewDepositModalCubitApprovedSnackBarMessage( - {required String tokenSymbol}); + String previewDepositModalCubitApprovedSnackBarMessage({ + required String tokenSymbol, + }); /// No description provided for @depositSuccessModalViewPositionOnDEX. /// @@ -1129,8 +1190,9 @@ S lookupS(Locale locale) { } throw FlutterError( - 'S.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.'); + 'S.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); } diff --git a/lib/l10n/gen/app_localizations_en.dart b/lib/l10n/gen/app_localizations_en.dart index 5ef9c0a..cec3d1a 100644 --- a/lib/l10n/gen/app_localizations_en.dart +++ b/lib/l10n/gen/app_localizations_en.dart @@ -16,6 +16,12 @@ class SEn extends S { @override String get twentyFourHours => '24h'; + @override + String get week => 'Week'; + + @override + String get weekCompact => '7d'; + @override String get appFooterTermsOfUse => 'Terms of Use'; @@ -39,8 +45,9 @@ class SEn extends S { String get understood => 'Understood'; @override - String depositPageShowingOnlyPoolsWithMoreThan( - {required String minLiquidity}) { + String depositPageShowingOnlyPoolsWithMoreThan({ + required String minLiquidity, + }) { return 'Showing only liquidity pools with more than $minLiquidity.'; } @@ -51,8 +58,9 @@ class SEn extends S { String get depositPageSearchAllPools => 'Search all pools?'; @override - String depositPageSearchOnlyForPoolsWithMorethan( - {required String minLiquidity}) { + String depositPageSearchOnlyForPoolsWithMorethan({ + required String minLiquidity, + }) { return 'Search only for pools with more than $minLiquidity?'; } @@ -121,8 +129,10 @@ class SEn extends S { } @override - String previewDepositModalDepositingIntoPool( - {required String baseTokenSymbol, required String quoteTokenSymbol}) { + String previewDepositModalDepositingIntoPool({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + }) { return 'Depositing into $baseTokenSymbol/$quoteTokenSymbol pool'; } @@ -141,8 +151,9 @@ class SEn extends S { String get previewDepositModalCurrentPrice => 'Current Price'; @override - String depositPageDepositSectionTokenNotNeeded( - {required String tokenSymbol}) { + String depositPageDepositSectionTokenNotNeeded({ + required String tokenSymbol, + }) { return '$tokenSymbol is not necessary for your selected range'; } @@ -157,8 +168,9 @@ class SEn extends S { 'Waiting transaction to be confirmed. '; @override - String previewDepositModalApproveSuccessSnackBarMessage( - {required String tokenSymbol}) { + String previewDepositModalApproveSuccessSnackBarMessage({ + required String tokenSymbol, + }) { return '$tokenSymbol approved successfully. '; } @@ -189,10 +201,11 @@ class SEn extends S { 'Contact us'; @override - String previewDepositModalDepositSuccessSnackBarMessage( - {required String baseTokenSymbol, - required String quoteTokenSymbol, - required String protocol}) { + String previewDepositModalDepositSuccessSnackBarMessage({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + required String protocol, + }) { return 'Successfully Deposited into the $baseTokenSymbol/$quoteTokenSymbol Pool at $protocol. '; } @@ -207,7 +220,12 @@ class SEn extends S { } @override - String get yieldCardYieldYearly => 'Yield (Yearly)'; + String get yieldCardYearlyYield => 'Yearly Yield'; + + @override + String yieldCardVisitProtocol({required String protocolName}) { + return 'Visit $protocolName'; + } @override String get yieldCardAverageYieldYearly => 'Average Yield (Yearly)'; @@ -280,8 +298,9 @@ class SEn extends S { String get exchangesFilterDropdownButtonTitle => 'Exchanges'; @override - String exchangesFilterDropdownButtonTitleNumered( - {required String exchangesCount}) { + String exchangesFilterDropdownButtonTitleNumered({ + required String exchangesCount, + }) { return 'Exchanges ($exchangesCount)'; } @@ -302,18 +321,28 @@ class SEn extends S { 'Pairing Token A and Token B to kick off the search for top yields!'; @override - String get depositPageLoadingStep2Title => 'Scanning the Pools...'; + String get depositPageLoadingStep2Title => 'Pair hunting…'; @override String get depositPageLoadingStep2Description => - 'Searching through the sea of pools for the best yields, hang tight!'; + 'Searching through more than a thousand pool combos… so you don\'t have to'; @override - String get depositPageLoadingStep3Title => - 'Fetching the Best yields for you...'; + String get depositPageLoadingStep3Title => 'Yield optimizer at work…'; @override String get depositPageLoadingStep3Description => + 'Scanning pools, calculating returns, and filtering the noise'; + + @override + String get depositPageBestYieldsIn => 'Best Yields in'; + + @override + String get depositPageLoadingStep4Title => + 'Fetching the Best yields for you...'; + + @override + String get depositPageLoadingStep4Description => 'Got it! Just adding a touch of sparkle to your perfect match!'; @override @@ -341,7 +370,7 @@ class SEn extends S { @override String get depositPageTimeFrameTooltipMessage => - 'Select a time-frame that matches your goal with this pool: a quick win (Short term), a balanced approach (Medium term), or a long haul (Long term).'; + 'Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.'; @override String get depositPageTimeFrameTooltipHelperButtonTitle => ' Learn more'; @@ -386,6 +415,13 @@ class SEn extends S { @override String get tokenSelectorModalTitle => 'Select a token'; + @override + String get tokenSelectorModalTokenGroups => 'Token Groups'; + + @override + String get tokenSelectorModalTokenGroupsTooltipMessage => + 'Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we\'ll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.'; + @override String get tokenSelectorModalDescription => 'Pick a token from our list or search by symbol or address to build your ideal liquidity pool!'; @@ -406,8 +442,9 @@ class SEn extends S { 'We hit a snag loading your token list. Give it another go, and if it keeps happening, feel free to reach us'; @override - String tokenSelectorModalSearchErrorDescription( - {required String searchedTerm}) { + String tokenSelectorModalSearchErrorDescription({ + required String searchedTerm, + }) { return 'We hit a snag while searching for a token matching $searchedTerm. Give it another go, and if it keeps happening, feel free to reach us'; } @@ -466,28 +503,27 @@ class SEn extends S { @override String positionsPageShowHideClosedPositions({required String isHidden}) { - String _temp0 = intl.Intl.selectLogic( - isHidden, - { - 'true': 'Show', - 'false': 'Hide', - 'other': 'Show/Hide', - }, - ); + String _temp0 = intl.Intl.selectLogic(isHidden, { + 'true': 'Show', + 'false': 'Hide', + 'other': 'Show/Hide', + }); return '$_temp0 closed positions'; } @override - String positionsPageNoPositionsInNetworkDescription( - {required String network}) { + String positionsPageNoPositionsInNetworkDescription({ + required String network, + }) { return 'It looks like you don’t have any positions in $network yet.\nGo ahead and create one to get started!'; } @override - String positionCardTokenPerToken( - {required String token0Qtd, - required String token0Symbol, - required String token1Symbol}) { + String positionCardTokenPerToken({ + required String token0Qtd, + required String token0Symbol, + required String token1Symbol, + }) { return '$token0Qtd $token0Symbol per $token1Symbol'; } @@ -552,8 +588,10 @@ class SEn extends S { String get depositSuccessModalDescriptionPart3 => 'on'; @override - String previewDepositModalCubitDepositingSnackBarMessage( - {required String token0Symbol, required String token1Symbol}) { + String previewDepositModalCubitDepositingSnackBarMessage({ + required String token0Symbol, + required String token1Symbol, + }) { return 'Depositing into $token0Symbol/$token1Symbol Pool...'; } @@ -561,8 +599,9 @@ class SEn extends S { String get previewDepositModalCubitApprovingSnackBarMessage => 'Approving...'; @override - String previewDepositModalCubitApprovedSnackBarMessage( - {required String tokenSymbol}) { + String previewDepositModalCubitApprovedSnackBarMessage({ + required String tokenSymbol, + }) { return 'Successfully approved $tokenSymbol'; } diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index de1ff3b..bbec3d0 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -6,35 +6,36 @@ abstract class ZupTheme { static String get fontFamily => "SNPro"; static ThemeData get lightTheme => ThemeData( - fontFamily: ZupTheme.fontFamily, - primaryColor: ZupColors.brand, - badgeTheme: const BadgeThemeData( - backgroundColor: ZupColors.brand, - textColor: ZupColors.white, - ), - inputDecorationTheme: const InputDecorationTheme( - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), - borderSide: BorderSide(color: ZupColors.red, width: 1.5), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), - borderSide: BorderSide(color: ZupColors.red, width: 1.5), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), - borderSide: BorderSide(color: ZupColors.brand, width: 1.5), - ), - ), - scrollbarTheme: - const ScrollbarThemeData(mainAxisMargin: 10, crossAxisMargin: 3, thickness: WidgetStatePropertyAll(5)), - scaffoldBackgroundColor: Colors.transparent, - textButtonTheme: ZupTextButtonTheme.lightTheme, - textSelectionTheme: const TextSelectionThemeData(selectionColor: ZupColors.brand5), - textTheme: const TextTheme( - titleSmall: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: ZupColors.black), - bodySmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: ZupColors.black), - bodyMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: ZupColors.black), - ), - ); + fontFamily: ZupTheme.fontFamily, + primaryColor: ZupColors.brand, + badgeTheme: const BadgeThemeData(backgroundColor: ZupColors.brand, textColor: ZupColors.white), + inputDecorationTheme: const InputDecorationTheme( + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + borderSide: BorderSide(color: ZupColors.red, width: 1.5), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + borderSide: BorderSide(color: ZupColors.red, width: 1.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + borderSide: BorderSide(color: ZupColors.brand, width: 1.5), + ), + ), + scrollbarTheme: const ScrollbarThemeData( + mainAxisMargin: 10, + crossAxisMargin: 3, + thickness: WidgetStatePropertyAll(5), + thumbVisibility: WidgetStatePropertyAll(false), + ), + scaffoldBackgroundColor: Colors.transparent, + textButtonTheme: ZupTextButtonTheme.lightTheme, + textSelectionTheme: const TextSelectionThemeData(selectionColor: ZupColors.brand5), + textTheme: const TextTheme( + titleSmall: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: ZupColors.black), + bodySmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: ZupColors.black), + bodyMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: ZupColors.black), + ), + ); } diff --git a/lib/widgets/token_avatar.dart b/lib/widgets/token_avatar.dart index 2d7a945..fa2b99a 100644 --- a/lib/widgets/token_avatar.dart +++ b/lib/widgets/token_avatar.dart @@ -30,7 +30,8 @@ class TokenAvatar extends StatelessWidget { Widget build(BuildContext context) { return asset.logoUrl.isEmpty ? genericAvatar() - : zupCachedImage.build(asset.logoUrl, + : zupCachedImage.build( + asset.logoUrl, height: size, width: size, radius: 50, @@ -41,6 +42,7 @@ class TokenAvatar extends StatelessWidget { backgroundColor: ZupColors.brand5, indicatorColor: ZupColors.brand, ), - )); + ), + ); } } diff --git a/lib/widgets/token_group_card.dart b/lib/widgets/token_group_card.dart new file mode 100644 index 0000000..57dc801 --- /dev/null +++ b/lib/widgets/token_group_card.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/gen/assets.gen.dart'; +import 'package:zup_app/widgets/token_avatar.dart'; +import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_ui_kit/zup_circular_loading_indicator.dart'; +import 'package:zup_ui_kit/zup_colors.dart'; +import 'package:zup_ui_kit/zup_selectable_card.dart'; +import 'package:zup_ui_kit/zup_tooltip.dart'; + +class TokenGroupCard extends StatefulWidget { + const TokenGroupCard({super.key, required this.group, required this.onClick}); + + final TokenGroupDto group; + final Function() onClick; + + @override + State createState() => _TokenGroupCardState(); +} + +class _TokenGroupCardState extends State { + final ZupCachedImage zupCachedImage = inject(); + bool isHovering = false; + + @override + Widget build(BuildContext context) { + return ZupSelectableCard( + onPressed: () => widget.onClick(), + onHoverChanged: (value) => setState(() => isHovering = value), + boxShadow: const [], + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + zupCachedImage.build( + widget.group.logoUrl, + height: 35, + width: 35, + radius: 35, + errorWidget: (_, __, ___) => Container( + height: 35, + width: 35, + color: ZupColors.gray6, + child: const Center( + child: Text("?", style: TextStyle(color: ZupColors.gray, fontSize: 16)), + ), + ), + placeholder: const Skeleton.ignore( + child: ZupCircularLoadingIndicator( + size: 35, + backgroundColor: ZupColors.brand5, + indicatorColor: ZupColors.brand, + ), + ), + ), + const SizedBox(width: 10), + Text( + widget.group.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: isHovering ? ZupColors.brand : ZupColors.gray, + ), + ), + const Spacer(), + ZupTooltip.widget( + tooltipChild: ScrollbarTheme( + data: ScrollbarThemeData( + thumbVisibility: WidgetStateProperty.all(true), + thickness: WidgetStateProperty.all(4.0), + thumbColor: WidgetStateProperty.all(ZupColors.gray5), + trackColor: WidgetStateProperty.all(ZupColors.gray5), + mainAxisMargin: 10, + ), + child: GridView.builder( + padding: const EdgeInsets.all(8), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 100, + mainAxisExtent: 40, + ), + itemBuilder: (_, index) => Padding( + padding: const EdgeInsets.all(3), + child: Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border.all(color: ZupColors.gray5), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + spacing: 10, + children: [ + TokenAvatar(asset: widget.group.tokens[index], size: 20), + Expanded( + child: Text( + overflow: TextOverflow.ellipsis, + widget.group.tokens[index].symbol, + style: const TextStyle(color: ZupColors.gray), + ), + ), + ], + ), + ), + ), + shrinkWrap: true, + itemCount: widget.group.tokens.length, + ), + ), + constraints: const BoxConstraints(maxHeight: 300, maxWidth: 200), + child: Assets.icons.infoCircle.svg( + colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), + height: 15, + width: 15, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/token_selector_button/token_selector_button.dart b/lib/widgets/token_selector_button/token_selector_button.dart index e889c3b..4322f5b 100644 --- a/lib/widgets/token_selector_button/token_selector_button.dart +++ b/lib/widgets/token_selector_button/token_selector_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/gen/assets.gen.dart'; import 'package:zup_app/l10n/gen/app_localizations.dart'; @@ -22,11 +23,14 @@ class TokenSelectorButton extends StatefulWidget { class _TokenSelectorButtonState extends State with DeviceInfoMixin { final zupCachedImage = inject(); TokenDto? get selectedToken => widget.controller.selectedToken; + TokenGroupDto? get selectedGroup => widget.controller.selectedTokenGroup; + + bool get hasSelection => widget.controller.hasSelection; bool isHovering = false; Color get getTextColor { - if (isHovering || selectedToken == null) return ZupColors.brand; + if (isHovering || !hasSelection) return ZupColors.brand; return ZupColors.black; } @@ -38,67 +42,93 @@ class _TokenSelectorButtonState extends State with DeviceIn @override Widget build(BuildContext context) { return StreamBuilder( - stream: widget.controller.selectedTokenStream, - builder: (context, _) { - return ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 1000), - child: MouseRegion( - onEnter: (event) => setState(() => isHovering = true), - onExit: (event) => setState(() => isHovering = false), - child: MaterialButton( - color: selectedToken != null ? ZupColors.gray6.withValues(alpha: 0.6) : ZupColors.brand6, - hoverColor: selectedToken != null ? ZupColors.gray6 : ZupColors.gray6.withValues(alpha: 0.2), - splashColor: selectedToken != null - ? ZupColors.gray5.withValues(alpha: 0.4) - : ZupColors.brand5.withValues(alpha: 0.5), - focusElevation: 0, - highlightElevation: 0, - elevation: 0, - hoverElevation: 0, - height: 100, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - padding: const EdgeInsets.all(30), - onPressed: () => TokenSelectorModal.show( - context, - showAsBottomSheet: isMobileSize(context), - onSelectToken: (token) { - widget.controller.changeToken(token); - }, - ), - child: Row( - children: [ - if (selectedToken == null) - Assets.icons.boltCircleFill.svg( - height: 16, - colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), - ) - else - TokenAvatar(asset: selectedToken!, size: 30), - const SizedBox(width: 10), - Expanded( - child: Text( - "${selectedToken?.name ?? S.of(context).selectToken} ${selectedToken != null ? "(${selectedToken?.symbol ?? ""})" : ""}", + stream: widget.controller.selectionStream, + builder: (context, _) { + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 1000), + child: MouseRegion( + onEnter: (event) => setState(() => isHovering = true), + onExit: (event) => setState(() => isHovering = false), + child: MaterialButton( + color: hasSelection ? ZupColors.gray6.withValues(alpha: 0.6) : ZupColors.brand6, + hoverColor: hasSelection ? ZupColors.gray6 : ZupColors.gray6.withValues(alpha: 0.2), + splashColor: hasSelection + ? ZupColors.gray5.withValues(alpha: 0.4) + : ZupColors.brand5.withValues(alpha: 0.5), + focusElevation: 0, + highlightElevation: 0, + elevation: 0, + hoverElevation: 0, + height: 100, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.all(30), + onPressed: () => TokenSelectorModal.show( + context, + showAsBottomSheet: isMobileSize(context), + onSelectTokenGroup: (group) { + widget.controller.changeTokenGroup(group); + }, + onSelectToken: (token) { + widget.controller.changeToken(token); + }, + ), + child: Row( + children: [ + if (!hasSelection) + Assets.icons.boltCircleFill.svg( + height: 16, + colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), + ) + else ...[ + if (selectedToken != null) TokenAvatar(asset: selectedToken!, size: 30), + if (selectedGroup != null) + zupCachedImage.build( + selectedGroup!.logoUrl, + height: 30, + width: 30, + radius: 50, + errorWidget: (_, __, ___) => Container( + height: 30, + width: 30, + color: ZupColors.gray6, + child: const Center( + child: Text("?", style: TextStyle(color: ZupColors.gray, fontSize: 16)), + ), + ), + ), + ], + const SizedBox(width: 10), + Expanded( + child: Text( + () { + if (selectedToken != null) return "${selectedToken?.name} (${selectedToken?.symbol})"; + if (selectedGroup != null) return "${selectedGroup?.name}"; + + return S.of(context).selectToken; + }(), style: TextStyle( fontSize: 17, color: getTextColor, - fontWeight: selectedToken == null ? FontWeight.w500 : null, - ), - )), - const SizedBox(width: 10), - AnimatedRotation( - duration: const Duration(milliseconds: 500), - curve: Curves.easeOutBack, - turns: isHovering ? 0.5 : 0, - child: Assets.icons.chevronDown.svg( - colorFilter: ColorFilter.mode(getChevronColor, BlendMode.srcIn), - height: 8, + fontWeight: !hasSelection ? FontWeight.w500 : null, ), ), - ], - ), + ), + const SizedBox(width: 10), + AnimatedRotation( + duration: const Duration(milliseconds: 500), + curve: Curves.easeOutBack, + turns: isHovering ? 0.5 : 0, + child: Assets.icons.chevronDown.svg( + colorFilter: ColorFilter.mode(getChevronColor, BlendMode.srcIn), + height: 8, + ), + ), + ], ), ), - ); - }); + ), + ); + }, + ); } } diff --git a/lib/widgets/token_selector_button/token_selector_button_controller.dart b/lib/widgets/token_selector_button/token_selector_button_controller.dart index 096049f..77653db 100644 --- a/lib/widgets/token_selector_button/token_selector_button_controller.dart +++ b/lib/widgets/token_selector_button/token_selector_button_controller.dart @@ -1,21 +1,43 @@ import 'dart:async'; +import 'package:async/async.dart' show StreamGroup; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; class TokenSelectorButtonController { - TokenSelectorButtonController({TokenDto? initialSelectedToken}) { - _selectedToken = initialSelectedToken; - } - TokenDto? _selectedToken; + TokenGroupDto? _selectedTokenGroup; + final StreamController _selectedTokenStreamController = StreamController.broadcast(); + final StreamController _selectedTokenGroupStreamController = + StreamController.broadcast(); + + bool get hasSelection => _selectedToken != null || _selectedTokenGroup != null; TokenDto? get selectedToken => _selectedToken; + TokenGroupDto? get selectedTokenGroup => _selectedTokenGroup; + Stream get selectedTokenStream => _selectedTokenStreamController.stream; + Stream get selectedTokenGroupStream => _selectedTokenGroupStreamController.stream; + Stream get selectionStream => StreamGroup.mergeBroadcast([selectedTokenStream, selectedTokenGroupStream]); void changeToken(TokenDto? newToken) { + if (newToken == _selectedToken) return; + _selectedToken = newToken; + _selectedTokenGroup = null; _selectedTokenStreamController.add(_selectedToken); + _selectedTokenGroupStreamController.add(null); + } + + void changeTokenGroup(TokenGroupDto? newTokenGroup) { + if (newTokenGroup == _selectedTokenGroup) return; + + _selectedToken = null; + _selectedTokenGroup = newTokenGroup; + + _selectedTokenGroupStreamController.add(_selectedTokenGroup); + _selectedTokenStreamController.add(null); } } diff --git a/lib/widgets/token_selector_modal/token_selector_modal.dart b/lib/widgets/token_selector_modal/token_selector_modal.dart index 6b0456d..542d718 100644 --- a/lib/widgets/token_selector_modal/token_selector_modal.dart +++ b/lib/widgets/token_selector_modal/token_selector_modal.dart @@ -3,31 +3,35 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/debouncer.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/gen/assets.gen.dart'; import 'package:zup_app/l10n/gen/app_localizations.dart'; import 'package:zup_app/widgets/token_card.dart'; +import 'package:zup_app/widgets/token_group_card.dart'; import 'package:zup_app/widgets/token_selector_modal/token_selector_modal_cubit.dart'; import 'package:zup_app/widgets/zup_skeletonizer.dart'; import 'package:zup_core/mixins/device_info_mixin.dart'; import 'package:zup_ui_kit/zup_ui_kit.dart'; class TokenSelectorModal extends StatefulWidget { - const TokenSelectorModal({super.key, required this.onSelectToken}); + const TokenSelectorModal({super.key, required this.onSelectToken, required this.onSelectTokenGroup}); final Function(TokenDto token) onSelectToken; + final Function(TokenGroupDto token) onSelectTokenGroup; static void show( BuildContext context, { required bool showAsBottomSheet, required Function(TokenDto token) onSelectToken, + required Function(TokenGroupDto token) onSelectTokenGroup, }) async { return ZupModal.show( context, showAsBottomSheet: showAsBottomSheet, content: BlocProvider.value( value: inject(), - child: TokenSelectorModal(onSelectToken: onSelectToken), + child: TokenSelectorModal(onSelectToken: onSelectToken, onSelectTokenGroup: onSelectTokenGroup), ), size: const Size(450, 600), title: S.of(context).tokenSelectorModalTitle, @@ -53,16 +57,40 @@ class _TokenSelectorModalState extends State with DeviceInfo Navigator.of(context).pop(); } - Widget _buildSectionTitle(String title, {double topPadding = 20}) => Padding( - padding: EdgeInsets.symmetric(horizontal: _horizontalPadding).copyWith(top: topPadding, bottom: 5), - child: Text(title, style: const TextStyle(fontSize: 14, color: ZupColors.gray)), - ); + void _selectTokenGroup(TokenGroupDto group) { + widget.onSelectTokenGroup(group); + Navigator.of(context).pop(); + } - Widget _buildSliverSectionTitle(String title, {double topPadding = 20}) { + Widget _buildSectionTitle(String title, {double topPadding = 20, String? tooltipMessage}) { + final text = Text(title, style: const TextStyle(fontSize: 14, color: ZupColors.gray)); + return Padding( + padding: EdgeInsets.symmetric(horizontal: _horizontalPadding).copyWith(top: topPadding, bottom: 5), + child: tooltipMessage == null + ? text + : ZupTooltip.text( + message: tooltipMessage, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 5, + children: [ + text, + Assets.icons.infoCircle.svg( + width: 14, + height: 14, + colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), + ), + ], + ), + ), + ); + } + + Widget _buildSliverSectionTitle(String title, {double topPadding = 20, String? tooltipMessage}) { return SliverToBoxAdapter( child: Align( alignment: Alignment.centerLeft, - child: _buildSectionTitle(title, topPadding: topPadding), + child: _buildSectionTitle(title, topPadding: topPadding, tooltipMessage: tooltipMessage), ), ); } @@ -113,8 +141,8 @@ class _TokenSelectorModalState extends State with DeviceInfo maxLines: 4, S.of(context).tokenSelectorModalSearchAlertForAllNetworks, style: const TextStyle(color: ZupColors.gray, fontSize: 12), - ) - ] + ), + ], ], ), ), @@ -125,7 +153,7 @@ class _TokenSelectorModalState extends State with DeviceInfo searchLoading: () => _buildSearchSuccessOrSearchLoadingSlivers(state), searchNotFound: (searchedTerm) => _buildSearchNotFoundSlivers(searchedTerm), searchError: (searchedTerm) => _buildSearchErrorSlivers(searchedTerm), - ) + ), ], ), ); @@ -134,145 +162,171 @@ class _TokenSelectorModalState extends State with DeviceInfo } List _buildErrorStateSlivers() => [ - SliverFillRemaining( - hasScrollBody: false, - child: Padding( - padding: const EdgeInsets.all(40), - child: Center( - child: ZupInfoState( - iconSize: 80, - icon: const Text(":(", style: TextStyle(color: ZupColors.brand)), - title: S.of(context).somethingWhenWrong, - description: S.of(context).tokenSelectorModalErrorDescription, - helpButtonTitle: S.of(context).letsGiveItAnotherShot, - helpButtonIcon: Assets.icons.arrowClockwise.svg(), - onHelpButtonTap: () => _cubit.fetchTokenList(), - ), - ), + SliverFillRemaining( + hasScrollBody: false, + child: Padding( + padding: const EdgeInsets.all(40), + child: Center( + child: ZupInfoState( + iconSize: 80, + icon: const Text(":(", style: TextStyle(color: ZupColors.brand)), + title: S.of(context).somethingWhenWrong, + description: S.of(context).tokenSelectorModalErrorDescription, + helpButtonTitle: S.of(context).letsGiveItAnotherShot, + helpButtonIcon: Assets.icons.arrowClockwise.svg(), + onHelpButtonTap: () => _cubit.fetchTokenList(), ), ), - ]; + ), + ), + ]; List _buildSearchErrorSlivers(String searchedTerm) => [ - SliverFillRemaining( - hasScrollBody: false, - child: Padding( - padding: const EdgeInsets.all(40), - child: Center( - child: ZupInfoState( - iconSize: 60, - icon: Assets.icons.sadMagnifyingglass.svg( - colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), - ), - title: S.of(context).somethingWhenWrong, - description: S.of(context).tokenSelectorModalSearchErrorDescription(searchedTerm: searchedTerm), - helpButtonTitle: S.of(context).letsGiveItAnotherShot, - helpButtonIcon: Assets.icons.arrowClockwise.svg(), - onHelpButtonTap: () => _cubit.searchToken(searchedTerm), - ), + SliverFillRemaining( + hasScrollBody: false, + child: Padding( + padding: const EdgeInsets.all(40), + child: Center( + child: ZupInfoState( + iconSize: 60, + icon: Assets.icons.sadMagnifyingglass.svg( + colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), ), + title: S.of(context).somethingWhenWrong, + description: S.of(context).tokenSelectorModalSearchErrorDescription(searchedTerm: searchedTerm), + helpButtonTitle: S.of(context).letsGiveItAnotherShot, + helpButtonIcon: Assets.icons.arrowClockwise.svg(), + onHelpButtonTap: () => _cubit.searchToken(searchedTerm), ), ), - ]; + ), + ), + ]; List _buildSearchNotFoundSlivers(String searchedTerm) => [ - SliverPadding( - padding: EdgeInsets.symmetric(horizontal: _horizontalPadding), - sliver: SliverFillRemaining( - hasScrollBody: false, - child: Center( - child: Text.rich( - textAlign: TextAlign.center, + SliverPadding( + padding: EdgeInsets.symmetric(horizontal: _horizontalPadding), + sliver: SliverFillRemaining( + hasScrollBody: false, + child: Center( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + style: const TextStyle(fontSize: 17), + children: [ TextSpan( - style: const TextStyle(fontSize: 17), - children: [ - TextSpan( - text: S.of(context).noResultsFor, - style: const TextStyle(color: ZupColors.gray, fontWeight: FontWeight.w400), - ), - TextSpan( - text: " \"$searchedTerm\"", - style: const TextStyle(color: ZupColors.black, fontWeight: FontWeight.w600), - ), - ], + text: S.of(context).noResultsFor, + style: const TextStyle(color: ZupColors.gray, fontWeight: FontWeight.w400), ), - ), + TextSpan( + text: " \"$searchedTerm\"", + style: const TextStyle(color: ZupColors.black, fontWeight: FontWeight.w600), + ), + ], ), ), - ) - ]; + ), + ), + ), + ]; List _buildSearchSuccessOrSearchLoadingSlivers(TokenSelectorModalState state) => [ - ZupSkeletonizer( - enabled: state == const TokenSelectorModalState.searchLoading(), - child: _buildSliverSectionTitle(S.of(context).searchResults, topPadding: 10), - ).sliver(), - SliverPadding( - padding: EdgeInsets.symmetric(horizontal: _horizontalPadding), - sliver: state.maybeWhen( - searchSuccess: (searchResult) => SliverList.builder( - itemCount: searchResult.length, - itemBuilder: (context, index) => Padding( - padding: _paddingBetweenListItems, - child: TokenCard( - asset: searchResult[index], - onClick: () => _selectToken(searchResult[index]), + ZupSkeletonizer( + enabled: state == const TokenSelectorModalState.searchLoading(), + child: _buildSliverSectionTitle(S.of(context).searchResults, topPadding: 10), + ).sliver(), + SliverPadding( + padding: EdgeInsets.symmetric(horizontal: _horizontalPadding), + sliver: state.maybeWhen( + searchSuccess: (searchResult) => SliverList.builder( + itemCount: searchResult.length, + itemBuilder: (context, index) => Padding( + padding: _paddingBetweenListItems, + child: TokenCard(asset: searchResult[index], onClick: () => _selectToken(searchResult[index])), + ), + ), + orElse: () => ZupSkeletonizer( + child: SliverList( + delegate: SliverChildListDelegate( + List.generate( + 3, + (index) => Padding( + padding: _paddingBetweenListItems, + child: TokenCard(asset: TokenDto.fixture(), onClick: () {}), ), ), ), - orElse: () => ZupSkeletonizer( - child: SliverList( - delegate: SliverChildListDelegate( - List.generate( - 3, - (index) => Padding( - padding: _paddingBetweenListItems, - child: TokenCard(asset: TokenDto.fixture(), onClick: () {}), - ), - ), - ), - ), - ).sliver(), ), - ), - ]; + ).sliver(), + ), + ), + ]; List _buildSuccessOrLoadingSlivers(TokenSelectorModalState state) => [ - state.maybeWhen( - success: (popularTokens) => _buildSliverSectionTitle(S.of(context).popularTokens), - orElse: () => const SliverToBoxAdapter(), + ...state.maybeWhen( + success: (tokenList) => [ + _buildSliverSectionTitle( + S.of(context).tokenSelectorModalTokenGroups, + tooltipMessage: S.of(context).tokenSelectorModalTokenGroupsTooltipMessage, ), + SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 20).copyWith(bottom: 20), - sliver: state.whenOrNull( - loading: () => ZupSkeletonizer( - child: SliverList( - delegate: SliverChildListDelegate( - List.generate( - 4, - (index) => Padding( - padding: _paddingBetweenListItems, - child: TokenCard(asset: TokenDto.fixture(), onClick: () {}), - ), - ), - )), - ).sliver(), - success: (popularTokens) => SliverList.builder( - itemCount: popularTokens.length, - itemBuilder: (context, index) { - final popularToken = popularTokens[index]; + sliver: SliverList.builder( + itemCount: tokenList.tokenGroups.length, + itemBuilder: (context, index) { + final group = tokenList.tokenGroups[index]; - return Padding( + return Padding( + padding: _paddingBetweenListItems, + child: TokenGroupCard( + key: Key("token-group-$index"), + group: group, + onClick: () => _selectTokenGroup(group), + ), + ); + }, + ), + ), + ], + orElse: () => [const SliverToBoxAdapter()], + ), + state.maybeWhen( + success: (popularTokens) => _buildSliverSectionTitle(S.of(context).popularTokens), + orElse: () => const SliverToBoxAdapter(), + ), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 20).copyWith(bottom: 20), + sliver: state.whenOrNull( + loading: () => ZupSkeletonizer( + child: SliverList( + delegate: SliverChildListDelegate( + List.generate( + 4, + (index) => Padding( padding: _paddingBetweenListItems, - child: TokenCard( - key: Key("popular-token-$index"), - asset: popularToken, - onClick: () => _selectToken(popularToken), - ), - ); - }, + child: TokenCard(asset: TokenDto.fixture(), onClick: () {}), + ), + ), ), ), + ).sliver(), + success: (tokenList) => SliverList.builder( + itemCount: tokenList.popularTokens.length, + itemBuilder: (context, index) { + final popularToken = tokenList.popularTokens[index]; + + return Padding( + padding: _paddingBetweenListItems, + child: TokenCard( + key: Key("popular-token-$index"), + asset: popularToken, + onClick: () => _selectToken(popularToken), + ), + ); + }, ), - ]; + ), + ), + ]; } diff --git a/lib/widgets/token_selector_modal/token_selector_modal_cubit.dart b/lib/widgets/token_selector_modal/token_selector_modal_cubit.dart index 694bfbc..2f21409 100644 --- a/lib/widgets/token_selector_modal/token_selector_modal_cubit.dart +++ b/lib/widgets/token_selector_modal/token_selector_modal_cubit.dart @@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:web3kit/web3kit.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_list_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; @@ -18,8 +19,8 @@ class TokenSelectorModalCubit extends Cubit { final AppCubit _appCubit; final Wallet _wallet; - final Map>> _tokenListCachedPerNetworkByAddress = {}; - Future?> get tokenList async => _tokenListCachedPerNetworkByAddress[_appCubit.selectedNetwork] + final Map> _tokenListCachedPerNetworkByAddress = {}; + Future get tokenList async => _tokenListCachedPerNetworkByAddress[_appCubit.selectedNetwork] ?[await _wallet.signer?.address ?? EthereumConstants.zeroAddress]; Future _emitSuccessCached() async => emit(TokenSelectorModalState.success((await tokenList)!)); @@ -41,7 +42,7 @@ class TokenSelectorModalCubit extends Cubit { emit(const TokenSelectorModalState.loading()); _tokenListCachedPerNetworkByAddress[_appCubit.selectedNetwork]![userAddress] = - await _tokensRepository.getPopularTokens(_appCubit.selectedNetwork); + await _tokensRepository.getTokenList(_appCubit.selectedNetwork); if (_shouldDiscardTokenListLoadedState) return; diff --git a/lib/widgets/token_selector_modal/token_selector_modal_state.dart b/lib/widgets/token_selector_modal/token_selector_modal_state.dart index 659ae76..a7e44d7 100644 --- a/lib/widgets/token_selector_modal/token_selector_modal_state.dart +++ b/lib/widgets/token_selector_modal/token_selector_modal_state.dart @@ -5,7 +5,7 @@ class TokenSelectorModalState with _$TokenSelectorModalState { const factory TokenSelectorModalState.initial() = _Initial; const factory TokenSelectorModalState.loading() = _Loading; const factory TokenSelectorModalState.searchLoading() = _SearchLoading; - const factory TokenSelectorModalState.success(List popularTokens) = _Success; + const factory TokenSelectorModalState.success(TokenListDto popularTokens) = _Success; const factory TokenSelectorModalState.searchSuccess(List result) = _SearchSuccess; const factory TokenSelectorModalState.error() = _Error; const factory TokenSelectorModalState.searchError(String searchedTerm) = _SearchError; diff --git a/lib/widgets/yield_card.dart b/lib/widgets/yield_card.dart index 5f24052..856a74f 100644 --- a/lib/widgets/yield_card.dart +++ b/lib/widgets/yield_card.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:intl/intl.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/extensions/num_extension.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/l10n/gen/app_localizations.dart'; +import 'package:zup_app/widgets/token_avatar.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; import 'package:zup_ui_kit/zup_ui_kit.dart'; @@ -15,12 +19,14 @@ class YieldCard extends StatefulWidget { required this.onChangeSelection, required this.isSelected, required this.timeFrame, + this.isHotestYield = false, }); final YieldDto currentYield; final bool isSelected; final Function(YieldDto? yield) onChangeSelection; final YieldTimeFrame timeFrame; + final bool isHotestYield; @override State createState() => _YieldCardState(); @@ -29,122 +35,167 @@ class YieldCard extends StatefulWidget { class _YieldCardState extends State { final zupCachedImage = inject(); final appCubit = inject(); + final infinityAnimationAutoPlay = inject(instanceName: InjectInstanceNames.infinityAnimationAutoPlay); final selectionAnimationDuration = const Duration(milliseconds: 150); - String get yieldTimeFramed { - if (widget.timeFrame.isDay) { - return widget.currentYield.yield24h.formatPercent; - } - - if (widget.timeFrame.isMonth) { - return widget.currentYield.yield30d.formatPercent; - } - - return widget.currentYield.yield90d.formatPercent; - } + Widget get yieldText => Text( + widget.currentYield.yieldTimeframed(widget.timeFrame).formatPercent, + style: const TextStyle(fontSize: 26, fontWeight: FontWeight.w600, color: ZupColors.black), + ); @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6), - child: Text( - S.of(context).yieldCardTimeFrameBest(timeFrame: widget.timeFrame.label(context)), - style: const TextStyle(color: ZupColors.gray, fontSize: 14, fontWeight: FontWeight.w600), - ), - ), - const SizedBox(height: 5), - ZupSelectableCard( - isSelected: widget.isSelected, - selectionAnimationDuration: selectionAnimationDuration, - onPressed: () => widget.onChangeSelection(widget.isSelected ? null : widget.currentYield), - padding: const EdgeInsets.all(10).copyWith(right: 0, top: 0), - width: double.maxFinite, - child: Stack( - children: [ - if (appCubit.selectedNetwork.isAllNetworks) - Positioned( - right: 2, - top: 2, - child: ZupTooltip( - message: S.of(context).yieldCardThisPoolIsAtNetwork(network: widget.currentYield.network.label), - trailingIcon: widget.currentYield.network.icon, - child: AnimatedContainer( - duration: selectionAnimationDuration, - height: 40, - padding: const EdgeInsets.all(6), - width: 40, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - blurStyle: BlurStyle.inner, - color: widget.isSelected ? ZupColors.brand5 : ZupColors.gray5, - blurRadius: 2, - spreadRadius: -2, - offset: const Offset(0, 0), - ), - BoxShadow( - color: widget.isSelected ? ZupColors.brand7 : ZupColors.white, - blurRadius: 5, - spreadRadius: -1, - offset: const Offset(2, -2), - ), - ], - borderRadius: BorderRadius.circular(8), + return ZupSelectableCard( + isSelected: widget.isSelected, + selectionAnimationDuration: selectionAnimationDuration, + + boxShadow: const [], + onPressed: () { + return widget.onChangeSelection(widget.isSelected ? null : widget.currentYield); + }, + padding: const EdgeInsets.all(10).copyWith(right: 0, top: 0, bottom: 0), + child: Stack( + children: [ + if (appCubit.selectedNetwork.isAllNetworks) + Positioned( + right: 2, + top: 2, + child: ZupTooltip.text( + message: S.of(context).yieldCardThisPoolIsAtNetwork(network: widget.currentYield.network.label), + trailingIcon: widget.currentYield.network.icon, + child: AnimatedContainer( + duration: selectionAnimationDuration, + height: 40, + padding: const EdgeInsets.all(6), + width: 40, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurStyle: BlurStyle.inner, + color: widget.isSelected ? ZupColors.brand5 : ZupColors.gray5, + blurRadius: 2, + spreadRadius: -2, + offset: const Offset(0, 0), ), - child: widget.currentYield.network.icon, - ), + BoxShadow( + color: widget.isSelected ? ZupColors.brand7 : ZupColors.white, + blurRadius: 5, + spreadRadius: -1, + offset: const Offset(2, -2), + ), + ], + borderRadius: BorderRadius.circular(8), ), + child: widget.currentYield.network.icon, ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Text(S.of(context).yieldCardYearlyYield, style: const TextStyle(fontSize: 14)), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.isHotestYield) ...[ + yieldText.animate( + effects: [ + const ScaleEffect( + duration: Duration(milliseconds: 200), + alignment: Alignment.center, + begin: Offset(1.1, 1.1), + end: Offset(1, 1), + ), + const ShimmerEffect( + duration: Duration(seconds: 2), + color: ZupColors.white, + curve: Curves.decelerate, + angle: 90, + size: 1, + ), + ], + autoPlay: infinityAnimationAutoPlay, + onComplete: (controller) => controller.repeat(reverse: true), + ), + ] else + yieldText, + ], + ), + Text( + "${NumberFormat.compactSimpleCurrency(decimalDigits: 2).format(widget.currentYield.totalValueLockedUSD)} ${S.of(context).tvl}", + style: const TextStyle(fontSize: 14, height: 1, color: ZupColors.gray), + ), + const SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only(top: 10), - child: Text( - S.of(context).yieldCardYieldYearly, - style: const TextStyle(fontSize: 14), + AnimatedContainer( + padding: const EdgeInsets.all(6), + duration: selectionAnimationDuration, + decoration: BoxDecoration( + color: widget.isSelected ? ZupColors.brand5 : ZupColors.gray6, + borderRadius: BorderRadius.circular(8), ), - ), - Text( - yieldTimeFramed, - style: const TextStyle(fontSize: 26, fontWeight: FontWeight.w600), - ), - Text( - "${NumberFormat.compactSimpleCurrency(decimalDigits: 2).format(widget.currentYield.totalValueLockedUSD)} ${S.of(context).tvl}", - style: const TextStyle( - fontSize: 14, - height: 1, - color: ZupColors.gray, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ZupMergedWidgets( + firstWidget: TokenAvatar(asset: widget.currentYield.token0, size: 25), + secondWidget: TokenAvatar(asset: widget.currentYield.token1, size: 25), + spacing: 0, + ), + const SizedBox(width: 10), + Text( + "${widget.currentYield.token0.symbol}/${widget.currentYield.token1.symbol}", + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), + ), + ], ), ), - const SizedBox(height: 10), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.currentYield.protocol.logo.isNotEmpty) - zupCachedImage.build( - widget.currentYield.protocol.logo, + const SizedBox(width: 10), + const Text(">", style: TextStyle(color: ZupColors.gray)), + const SizedBox(width: 10), + if (widget.currentYield.protocol.logo.isNotEmpty) + zupCachedImage.build( + widget.currentYield.protocol.logo, + height: 25, + width: 25, + radius: 50, + errorWidget: (context, error, stackTrace) { + return Container( height: 25, width: 25, - radius: 50, - ), - const SizedBox(width: 5), - Text( + decoration: BoxDecoration(color: ZupColors.gray6, borderRadius: BorderRadius.circular(50)), + ); + }, + ), + const SizedBox(width: 5), + Flexible( + child: ZupTooltip.text( + message: "", + helperButtonTitle: S + .of(context) + .yieldCardVisitProtocol(protocolName: widget.currentYield.protocol.name), + helperButtonOnPressed: () => launchUrl(Uri.parse(widget.currentYield.protocol.url)), + child: Text( widget.currentYield.protocol.name, + overflow: TextOverflow.ellipsis, + maxLines: 1, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), - ], - ) + ), + ), + const SizedBox(width: 20), ], ), ], ), - ), - ], + ], + ), ); } } diff --git a/lib/widgets/zup_cached_image.dart b/lib/widgets/zup_cached_image.dart index aedfc61..b5ebc4a 100644 --- a/lib/widgets/zup_cached_image.dart +++ b/lib/widgets/zup_cached_image.dart @@ -18,19 +18,20 @@ class ZupCachedImage { Widget? placeholder, ImageErrorWidgetBuilder? errorWidget, }) { - return ClipRRect( - key: Key(url), - borderRadius: BorderRadius.circular(radius ?? 0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(radius ?? 0), - border: Border.all(width: 0.5, color: ZupColors.gray5), - ), - // cache not implemented yet because of web issue rendering images from other domains (https://github.com/Baseflow/flutter_cached_network_image/issues/972) + return Container( + key: ValueKey(url), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(radius ?? 0), + border: Border.all(width: 0.5, color: ZupColors.gray5), + ), + // cache not implemented yet because of web issue rendering images from other domains (https://github.com/Baseflow/flutter_cached_network_image/issues/972) + child: ClipRRect( + borderRadius: BorderRadius.circular(radius ?? 0), child: Image.network( _parseImageUrl(url), height: height, width: width, + fit: BoxFit.cover, errorBuilder: errorWidget, frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { diff --git a/pubspec.lock b/pubspec.lock index b542fc9..956c4fc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "85.0.0" _flutterfire_internals: dependency: transitive description: @@ -17,19 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.55" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.7.1" ansicolor: dependency: transitive description: @@ -55,7 +50,7 @@ packages: source: hosted version: "2.7.0" async: - dependency: transitive + dependency: "direct main" description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" @@ -82,10 +77,10 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "3.0.0" build_config: dependency: transitive description: @@ -106,26 +101,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.0" build_runner: dependency: "direct main" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.6.0" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62 url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "9.2.0" built_collection: dependency: transitive description: @@ -138,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62" url: "https://pub.dev" source: hosted - version: "8.9.5" + version: "8.11.0" cached_network_image: dependency: "direct main" description: @@ -250,10 +245,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.1.1" dartx: dependency: transitive description: @@ -266,10 +261,10 @@ packages: dependency: "direct main" description: name: decimal - sha256: "28239b8b929c1bd8618702e6dbc96e2618cf99770bbe9cb040d6cf56a11e4ec3" + sha256: fc706a5618b81e5b367b01dd62621def37abc096f2b46a9bd9068b64c1fa36d0 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.4" dio: dependency: "direct main" description: @@ -297,10 +292,11 @@ packages: envied_generator: dependency: "direct dev" description: - name: envied_generator - sha256: "894f6c5eb624c60a1ce6f642b6fd7ec68bc3440aa6f1881837aa9acbbeade0c8" - url: "https://pub.dev" - source: hosted + path: "packages/envied_generator" + ref: HEAD + resolved-ref: "5cc9172f320f5b4293417358d57e039bf8e90c25" + url: "https://github.com/petercinibulk/envied.git" + source: git version: "1.1.1" equatable: dependency: "direct main" @@ -423,18 +419,18 @@ packages: dependency: transitive description: name: flutter_gen_core - sha256: "3eaa2d3d8be58267ac4cd5e215ac965dd23cae0410dc073de2e82e227be32bfc" + sha256: eda54fdc5de08e7eeea663eb8442aafc8660b5a13fda4e0c9e572c64e50195fb url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.11.0" flutter_gen_runner: dependency: "direct main" description: name: flutter_gen_runner - sha256: e74b4ead01df3e8f02e73a26ca856759dbbe8cb3fd60941ba9f4005cd0cd19c9 + sha256: "669bf8b7a9b4acbdcb7fcc5e12bf638aca19acedf43341714cbca3bf3a219521" url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.11.0" flutter_lints: dependency: "direct dev" description: @@ -460,10 +456,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 + sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -478,18 +474,18 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "3.2.0" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -578,6 +574,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" image_size_getter: dependency: transitive description: @@ -602,14 +606,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -622,10 +618,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27 url: "https://pub.dev" source: hosted - version: "6.9.0" + version: "6.10.0" leak_tracker: dependency: transitive description: @@ -674,14 +670,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.1" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -911,7 +899,7 @@ packages: description: path: "." ref: hotfix-vercel - resolved-ref: c43d65f57143605eaa2b7c4d9be753f2baa50307 + resolved-ref: e650b6ab6c14ecd419761f1d406c05dbe60075b1 url: "https://github.com/RyanHolanda/routefly.git" source: git version: "3.1.3" @@ -999,10 +987,10 @@ packages: dependency: "direct main" description: name: skeletonizer - sha256: a9ddf63900947f4c0648372b6e9987bc2b028db9db843376db6767224d166c31 + sha256: eebc03dc86b298e2d7f61e0ebce5713e9dbbc3e786f825909b4591756f196eb6 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0+1" sky_engine: dependency: transitive description: flutter @@ -1012,18 +1000,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: fc787b1f89ceac9580c3616f899c9a447413cbdac1df071302127764c023a134 url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "3.0.0" source_helper: dependency: transitive description: name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + sha256: "4f81479fe5194a622cdd1713fe1ecb683a6e6c85cd8cec8e2e35ee5ab3fdf2a1" url: "https://pub.dev" source: hosted - version: "1.3.5" + version: "1.3.6" source_span: dependency: transitive description: @@ -1293,7 +1281,7 @@ packages: description: path: "." ref: main - resolved-ref: "50b4737ccfa02d8b657173e430ca197dc18b5993" + resolved-ref: "3f66c1d786bec0f4135b4a12ddad02983073cb1f" url: "https://github.com/Zup-Protocol/web3kit.git" source: git version: "0.0.1" @@ -1342,7 +1330,7 @@ packages: description: path: "." ref: main - resolved-ref: "93b849fffe3a50e116c28d45530ffb28a007c5e1" + resolved-ref: "7c9bde523f7fc3d7d15d2fddcd2161faf1589e8d" url: "https://github.com/Zup-Protocol/zup-core.git" source: git version: "0.0.1" @@ -1351,10 +1339,10 @@ packages: description: path: "." ref: main - resolved-ref: "1d2a3b3890032536df60a14db88bf7cd8fe7e52e" + resolved-ref: "034a680b84ef0436dd8c83e44291016b6abe92b9" url: "https://github.com/Zup-Protocol/zup-ui-kit.git" source: git version: "0.0.1" sdks: - dart: ">=3.7.0 <4.0.0" - flutter: ">=3.32.2" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.32.8" diff --git a/pubspec.yaml b/pubspec.yaml index e62a16f..63e0a00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,18 +2,18 @@ name: zup_app publish_to: none environment: - sdk: ^3.5.2 - flutter: 3.32.2 + sdk: ^3.8.0 + flutter: 3.32.8 dependencies: build_runner: ^2.4.12 flutter: sdk: flutter - flutter_gen_runner: ^5.7.0 - flutter_svg: ^2.0.10+1 + flutter_gen_runner: ^5.11.0 + flutter_svg: ^2.2.0 routefly: git: - url: https://github.com/RyanHolanda/routefly.git # TODO: Pull request sent to the original repo, wait for merge and then update the dependency for the original. + url: https://github.com/RyanHolanda/routefly.git # TODO: Issue sent to the original repo, to fix vercel build (https://github.com/Flutterando/routefly/issues/28`) ref: hotfix-vercel flutter_web_plugins: sdk: flutter @@ -32,48 +32,52 @@ dependencies: git: url: https://github.com/Zup-Protocol/zup-core.git ref: main - freezed_annotation: ^2.4.4 + freezed_annotation: ^3.1.0 flutter_bloc: ^8.1.6 collection: ^1.18.0 json_annotation: ^4.9.0 url_launcher: ^6.3.0 cached_network_image: ^3.4.1 - skeletonizer: ^2.0.1 + skeletonizer: ^2.1.0+1 shared_preferences: ^2.3.2 dio: ^5.7.0 intl: ^0.20.2 - lottie: ^3.1.2 + lottie: ^3.3.1 equatable: ^2.0.5 - decimal: ^3.0.2 - flutter_animate: ^4.5.0 + decimal: ^3.2.4 + flutter_animate: ^4.5.2 clock: ^1.1.1 confetti: ^0.8.0 currency_text_input_formatter: ^2.3.0 firebase_core: ^3.13.0 firebase_analytics: ^11.4.5 envied: ^1.1.1 + async: ^2.13.0 # dependency_overrides: -# zup_core: -# path: ../zup-core -# web3kit: -# path: ../web3kit -# zup_ui_kit: -# path: ../zup-ui-kit +# zup_core: +# path: ../zup-core +# web3kit: +# path: ../web3kit +# zup_ui_kit: +# path: ../zup-ui-kit dev_dependencies: flutter_test: sdk: flutter fake_async: ^1.3.1 flutter_lints: ^4.0.0 - freezed: ^2.5.7 + freezed: ^3.2.0 golden_toolkit: ^0.15.0 mocktail_image_network: ^1.2.0 mocktail: ^1.0.4 - json_serializable: ^6.8.0 + json_serializable: ^6.10.0 url_launcher_platform_interface: ^2.3.2 plugin_platform_interface: ^2.1.8 - envied_generator: ^1.1.1 + envied_generator: + git: + url: https://github.com/petercinibulk/envied.git + path: packages/envied_generator flutter_gen: output: lib/gen/ diff --git a/test/app/create/create_page_select_token_stage_test.dart b/test/app/create/create_page_select_token_stage_test.dart index 1e167ae..1e32f86 100644 --- a/test/app/create/create_page_select_token_stage_test.dart +++ b/test/app/create/create_page_select_token_stage_test.dart @@ -11,6 +11,8 @@ import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/debouncer.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; +import 'package:zup_app/core/dtos/token_list_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/core/repositories/protocol_repository.dart'; @@ -58,158 +60,174 @@ void main() { when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto.fixture()); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); when(() => appCubit.selectedNetworkStream).thenAnswer((_) => const Stream.empty()); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => [ - TokenDto.fixture(), - TokenDto.fixture(), - ]); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto.fixture()); }); tearDown(() => inject.reset()); Future goldenBuilder({bool isMobile = false}) async => await goldenDeviceBuilder( - const CreatePageSelectTokensStage(), - device: isMobile ? GoldenDevice.mobile : GoldenDevice.pc, - ); + const CreatePageSelectTokensStage(), + device: isMobile ? GoldenDevice.mobile : GoldenDevice.pc, + ); zGoldenTest( - "When loading the page, it should select only the A token as the default token for the selected network (the token B should not be selected)", - goldenFileName: "create_page_select_tokens_stage_default_a_token", (tester) async { - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); + "When loading the page, it should select only the A token as the default token for the selected network (the token B should not be selected)", + goldenFileName: "create_page_select_tokens_stage_default_a_token", + (tester) async { + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - }); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); - zGoldenTest("When the device is mobile, it should have a horizontal padding, and less padding on the top", - goldenFileName: "create_page_select_tokens_stage_mobile", (tester) async { - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); + zGoldenTest( + "When the device is mobile, it should have a horizontal padding, and less padding on the top", + goldenFileName: "create_page_select_tokens_stage_mobile", + (tester) async { + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); - await tester.pumpAndSettle(); - }); + await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( - "When selecting the B token with the same address as A token, it should change the A token to null, and the B token to the selected token", - goldenFileName: "create_page_select_tokens_stage_change_b_token_to_same_token_as_a", (tester) async { - const selectedNetwork = AppNetworks.sepolia; - final token0 = TokenDto.fixture(); + "When selecting the B token with the same address as A token, it should change the A token to null, and the B token to the selected token", + goldenFileName: "create_page_select_tokens_stage_change_b_token_to_same_token_as_a", + (tester) async { + const selectedNetwork = AppNetworks.sepolia; + when(() => appCubit.selectedNetwork).thenReturn(selectedNetwork); + when(() => appCubit.currentChainId).thenReturn(selectedNetwork.chainId); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer( - (_) async => [token0], - ); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("token-a-selector"))); + await tester.pumpAndSettle(); - when(() => appCubit.selectedNetwork).thenReturn(selectedNetwork); + await tester.tap(find.byType(TokenCard).first); + await tester.pumpAndSettle(); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("token-b-selector"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("token-b-selector"))); + await tester.pumpAndSettle(); - await tester.tap(find.byType(TokenCard).first); - await tester.pumpAndSettle(); - }); + await tester.tap(find.byType(TokenCard).first); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( - "When selecting the A token with the same address as B token, it should change the B token to null and the A token to the selected token", - goldenFileName: "create_page_select_tokens_stage_change_a_token_to_same_token_as_b", (tester) async { - const selectedNetwork = AppNetworks.sepolia; - final token0 = TokenDto.fixture(); - - when(() => appCubit.currentChainId).thenReturn(selectedNetwork.chainId); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer( - (_) async => [token0], - ); + "When selecting the A token with the same address as B token, it should change the B token to null and the A token to the selected token", + goldenFileName: "create_page_select_tokens_stage_change_a_token_to_same_token_as_b", + (tester) async { + const selectedNetwork = AppNetworks.sepolia; + when(() => appCubit.currentChainId).thenReturn(selectedNetwork.chainId); - when(() => appCubit.selectedNetwork).thenReturn(selectedNetwork); + when(() => appCubit.selectedNetwork).thenReturn(selectedNetwork); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - /// making token A to be null by selecting the same token as A in token B - await tester.tap(find.byKey(const Key("token-b-selector"))); - await tester.pumpAndSettle(); - await tester.tap(find.byType(TokenCard).first); - await tester.pumpAndSettle(); + /// making token A to be null by selecting the same token as A in token B + await tester.tap(find.byKey(const Key("token-b-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenCard).first); + await tester.pumpAndSettle(); - /// just to make sure that the change ocurred, so it is using the same golden file as the inverse test - await screenMatchesGolden(tester, "create_page_select_tokens_stage_change_b_token_to_same_token_as_a"); + /// just to make sure that the change ocurred, so it is using the same golden file as the inverse test + await screenMatchesGolden(tester, "create_page_select_tokens_stage_change_b_token_to_same_token_as_a"); - /// real test - await tester.tap(find.byKey(const Key("token-a-selector"))); - await tester.pumpAndSettle(); - await tester.tap(find.byType(TokenCard).first); - await tester.pumpAndSettle(); - }); + /// real test + await tester.tap(find.byKey(const Key("token-a-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenCard).first); + await tester.pumpAndSettle(); + }, + ); - zGoldenTest("When the token A is selected, but the token B is not, the button to find liquidity should be disabled", - goldenFileName: "create_page_select_tokens_stage_token_a_selected_disabled_button", (tester) async { - const selectedNetwork = AppNetworks.sepolia; + zGoldenTest( + "When the token A is selected, but the token B is not, the button to find liquidity should be disabled", + goldenFileName: "create_page_select_tokens_stage_token_a_selected_disabled_button", + (tester) async { + const selectedNetwork = AppNetworks.sepolia; - when(() => appCubit.selectedNetwork).thenReturn(selectedNetwork); + when(() => appCubit.selectedNetwork).thenReturn(selectedNetwork); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("token-a-selector"))); - await tester.pumpAndSettle(); - await tester.tap(find.byType(TokenCard).first); - await tester.pumpAndSettle(); - }); + await tester.tap(find.byKey(const Key("token-a-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenCard).first); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( - "When the token B is selected, and the token A is also selected, the button to find liquidity should be enabled", - goldenFileName: "create_page_select_tokens_stage_token_enabled_button", (tester) async { - const token0Name = "Token1"; - const token1Name = "Token2"; - - when(() => appCubit.currentChainId).thenAnswer((_) => appCubit.selectedNetwork.chainId); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => ([ - TokenDto(addresses: {appCubit.selectedNetwork.chainId: "token1"}, name: "Token1"), - TokenDto(addresses: { - appCubit.selectedNetwork.chainId: "token2", - }, name: "Token2"), - ])); - - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - - await tester.tap(find.byKey(const Key("token-a-selector"))); - await tester.pumpAndSettle(); - await tester.tap(find.text(token0Name)); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("token-b-selector"))); - await tester.pumpAndSettle(); - await tester.tap(find.text(token1Name)); - await tester.pumpAndSettle(); - }); + "When the token B is selected, and the token A is also selected, the button to find liquidity should be enabled", + goldenFileName: "create_page_select_tokens_stage_token_enabled_button", + (tester) async { + const token0Name = "Token1"; + const token1Name = "Token2"; + + when(() => appCubit.currentChainId).thenAnswer((_) => appCubit.selectedNetwork.chainId); + when(() => tokensRepository.getTokenList(any())).thenAnswer( + (_) async => (TokenListDto( + popularTokens: [ + TokenDto(addresses: {appCubit.selectedNetwork.chainId: token1Name}, name: token0Name), + TokenDto(addresses: {appCubit.selectedNetwork.chainId: token0Name}, name: token1Name), + ], + )), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + + await tester.tap(find.byKey(const Key("token-a-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.text(token0Name)); + await tester.pumpAndSettle(); - zGoldenTest("""When tokens are selected, but the app cubit notify about the app network change, - it should reset the tokens.""", goldenFileName: "create_page_select_tokens_stage_reset_tokens_from_network", - (tester) async { - final networkStream = StreamController(); + await tester.tap(find.byKey(const Key("token-b-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.text(token1Name)); + await tester.pumpAndSettle(); + }, + ); - when(() => appCubit.selectedNetworkStream).thenAnswer((_) => networkStream.stream); - when(() => appCubit.currentChainId).thenAnswer((_) => appCubit.selectedNetwork.chainId); + zGoldenTest( + """When tokens are selected, but the app cubit notify about the app network change, + it should reset the tokens.""", + goldenFileName: "create_page_select_tokens_stage_reset_tokens_from_network", + (tester) async { + final networkStream = StreamController(); - const token0Name = "Token1"; - const token1Name = "Token2"; + when(() => appCubit.selectedNetworkStream).thenAnswer((_) => networkStream.stream); + when(() => appCubit.currentChainId).thenAnswer((_) => appCubit.selectedNetwork.chainId); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => [ - TokenDto(addresses: {appCubit.selectedNetwork.chainId: "token1"}, name: "Token1"), - TokenDto(addresses: {appCubit.selectedNetwork.chainId: "token2"}, name: "Token2"), - ]); + const token0Name = "Token1"; + const token1Name = "Token2"; - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + when(() => tokensRepository.getTokenList(any())).thenAnswer( + (_) async => (TokenListDto( + popularTokens: [ + TokenDto(addresses: {appCubit.selectedNetwork.chainId: "token1"}, name: "Token1"), + TokenDto(addresses: {appCubit.selectedNetwork.chainId: "token2"}, name: "Token2"), + ], + )), + ); - await tester.tap(find.byKey(const Key("token-a-selector"))); - await tester.pumpAndSettle(); - await tester.tap(find.text(token0Name)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("token-b-selector"))); - await tester.pumpAndSettle(); - await tester.tap(find.text(token1Name)); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("token-a-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.text(token0Name)); + await tester.pumpAndSettle(); - networkStream.add(AppNetworks.mainnet); - }); + await tester.tap(find.byKey(const Key("token-b-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.text(token1Name)); + await tester.pumpAndSettle(); + + networkStream.add(AppNetworks.mainnet); + }, + ); zGoldenTest( "When the saved pool search settings are not the default ones, a badge should be shown in the config button", @@ -280,15 +298,29 @@ void main() { const token1Id = "87"; final tokens = [ - TokenDto.fixture() - .copyWith(name: "TokenA", internalId: token0Id, addresses: {appCubit.selectedNetwork.chainId: token0Id}), - TokenDto.fixture() - .copyWith(name: "TokenB", internalId: token1Id, addresses: {appCubit.selectedNetwork.chainId: token1Id}), + TokenDto.fixture().copyWith( + name: "TokenA", + internalId: token0Id, + addresses: {appCubit.selectedNetwork.chainId: token0Id}, + ), + TokenDto.fixture().copyWith( + name: "TokenB", + internalId: token1Id, + addresses: {appCubit.selectedNetwork.chainId: token1Id}, + ), ]; when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.allNetworks); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokens); - when(() => zupNavigator.navigateToDeposit(any(), any(), any())).thenAnswer((_) async {}); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto(popularTokens: tokens)); + when( + () => zupNavigator.navigateToDeposit( + group0: any(named: "group0"), + group1: any(named: "group1"), + network: any(named: "network"), + token0: any(named: "token0"), + token1: any(named: "token1"), + ), + ).thenAnswer((_) async {}); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); @@ -305,7 +337,59 @@ void main() { await tester.tap(find.byKey(const Key("search-button"))); await tester.pumpAndSettle(); - verify(() => zupNavigator.navigateToDeposit(token0Id, token1Id, AppNetworks.allNetworks)); + verify( + () => zupNavigator.navigateToDeposit( + token0: token0Id, + token1: token1Id, + network: AppNetworks.allNetworks, + group0: null, + group1: null, + ), + ).called(1); + }, + ); + + zGoldenTest( + "When selecting two token groups, and then clicking to search, it should pass the groups ids to the next stage", + (tester) async { + final tokenGroups = [TokenGroupDto.fixture(), const TokenGroupDto(id: "group2", name: "Group 2")]; + + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.allNetworks); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto(tokenGroups: tokenGroups)); + when( + () => zupNavigator.navigateToDeposit( + group0: any(named: "group0"), + group1: any(named: "group1"), + network: any(named: "network"), + token0: any(named: "token0"), + token1: any(named: "token1"), + ), + ).thenAnswer((_) async {}); + + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + + await tester.tap(find.byKey(const Key("token-a-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.text(tokenGroups[0].name).first); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("token-b-selector"))); + await tester.pumpAndSettle(); + await tester.tap(find.text(tokenGroups[1].name).first); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("search-button"))); + await tester.pumpAndSettle(); + + verify( + () => zupNavigator.navigateToDeposit( + token0: null, + token1: null, + network: AppNetworks.allNetworks, + group0: tokenGroups[0].id, + group1: tokenGroups[1].id, + ), + ).called(1); }, ); } diff --git a/test/app/create/deposit/deposit_cubit_test.dart b/test/app/create/deposit/deposit_cubit_test.dart index 541c0bf..195ff3f 100644 --- a/test/app/create/deposit/deposit_cubit_test.dart +++ b/test/app/create/deposit/deposit_cubit_test.dart @@ -50,29 +50,29 @@ void main() { appCubit = AppCubitMock(); zupAnalytics = ZupAnalyticsMock(); - sut = DepositCubit( - yieldRepository, - zupSingletonCache, - wallet, - cache, - appCubit, - zupAnalytics, - poolService, - ); + sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, appCubit, zupAnalytics, poolService); when(() => appCubit.isTestnetMode).thenReturn(false); when(() => cache.blockedProtocolsIds).thenReturn([]); - when(() => yieldRepository.getAllNetworksYield( + when( + () => yieldRepository.getAllNetworksYield( token0InternalId: any(named: "token0InternalId"), token1InternalId: any(named: "token1InternalId"), searchSettings: any(named: "searchSettings"), blockedProtocolIds: any(named: "blockedProtocolIds"), - testnetMode: any(named: "testnetMode"))).thenAnswer((_) async => YieldsDto.fixture()); + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + testnetMode: any(named: "testnetMode"), + ), + ).thenAnswer((_) async => YieldsDto.fixture()); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto.fixture()); when( - () => uniswapV3Pool.fromRpcProvider(contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl")), + () => uniswapV3Pool.fromRpcProvider( + contractAddress: any(named: "contractAddress"), + rpcUrl: any(named: "rpcUrl"), + ), ).thenReturn(uniswapV3PoolImpl); when( @@ -80,18 +80,22 @@ void main() { network: any(named: "network"), token0: any(named: "token0"), token1: any(named: "token1"), + group0: any(named: "group0"), + group1: any(named: "group1"), ), ).thenAnswer((_) async {}); - when(() => uniswapV3PoolImpl.slot0()).thenAnswer((_) async => ( - feeProtocol: BigInt.zero, - observationCardinality: BigInt.zero, - observationCardinalityNext: BigInt.zero, - observationIndex: BigInt.zero, - sqrtPriceX96: BigInt.zero, - tick: poolTick, - unlocked: true - )); + when(() => uniswapV3PoolImpl.slot0()).thenAnswer( + (_) async => ( + feeProtocol: BigInt.zero, + observationCardinality: BigInt.zero, + observationCardinalityNext: BigInt.zero, + observationIndex: BigInt.zero, + sqrtPriceX96: BigInt.zero, + tick: poolTick, + unlocked: true, + ), + ); when(() => poolService.getPoolTick(any())).thenAnswer((_) async => poolTick); when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); @@ -108,8 +112,7 @@ void main() { const minutesPassed = 3; final selectedYield = YieldDto.fixture(); - const selectedTimeframe = YieldTimeFrame.day; - await sut.selectYield(selectedYield, selectedTimeframe); + await sut.selectYield(selectedYield); fakeAsync((async) { sut.setup(); @@ -126,140 +129,336 @@ void main() { }); }); - test("""And when the minuted passed, but the selected yield is null - it should not execute the task to get the pool tick""", () async { - BigInt? actualLastEmittedPoolTick; - int eventsCounter = 0; - const minutesPassed = 3; + test( + """And when the minuted passed, but the selected yield is null + it should not execute the task to get the pool tick""", + () async { + BigInt? actualLastEmittedPoolTick; + int eventsCounter = 0; + const minutesPassed = 3; - await sut.selectYield(null, null); + await sut.selectYield(null); - fakeAsync((async) { - sut.setup(); + fakeAsync((async) { + sut.setup(); - sut.poolTickStream.listen((event) { - actualLastEmittedPoolTick = event; - eventsCounter++; - }); + sut.poolTickStream.listen((event) { + actualLastEmittedPoolTick = event; + eventsCounter++; + }); - async.elapse(const Duration(minutes: minutesPassed)); + async.elapse(const Duration(minutes: minutesPassed)); - expect(actualLastEmittedPoolTick, null); - expect(eventsCounter, 0); - }); - }); + expect(actualLastEmittedPoolTick, null); + expect(eventsCounter, 0); + }); + }, + ); - test("""If the cubit is closed, and the minuted passed, + test( + """If the cubit is closed, and the minuted passed, it should not execute the task to get the pool tick - and cancel the periodic task""", () async { - final selectedYield = YieldDto.fixture(); - await sut.selectYield(selectedYield, YieldTimeFrame.day); - int eventCount = 0; + and cancel the periodic task""", + () async { + final selectedYield = YieldDto.fixture(); + await sut.selectYield(selectedYield); + int eventCount = 0; - fakeAsync((async) { - sut.setup(); - sut.close(); + fakeAsync((async) { + sut.setup(); + sut.close(); - sut.poolTickStream.listen((_) { - eventCount++; - }); + sut.poolTickStream.listen((_) { + eventCount++; + }); - async.elapse(const Duration(minutes: 10)); + async.elapse(const Duration(minutes: 10)); - expect(async.periodicTimerCount, 0); - expect(eventCount, 0); - }); - }); + expect(async.periodicTimerCount, 0); + expect(eventCount, 0); + }); + }, + ); }); test("When calling `getBestPools` it should emit the loading state", () async { expectLater(sut.stream, emits(const DepositState.loading())); - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: ""); + await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: "", group1Id: ""); }); test("When calling `getBestPools` it should call the yield repository to get the best pools", () async { - when(() => yieldRepository.getSingleNetworkYield( + when( + () => yieldRepository.getSingleNetworkYield( token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), searchSettings: any(named: "searchSettings"), blockedProtocolIds: any(named: "blockedProtocolIds"), - network: any(named: "network"))).thenAnswer( - (_) async => YieldsDto.fixture(), - ); + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + network: any(named: "network"), + ), + ).thenAnswer((_) async => YieldsDto.fixture()); const token0Address = "token0Address"; const token1Address = "token1Address"; - await sut.getBestPools(token0AddressOrId: token0Address, token1AddressOrId: token1Address); + await sut.getBestPools( + token0AddressOrId: token0Address, + token1AddressOrId: token1Address, + group0Id: null, + group1Id: null, + ); + + verify( + () => yieldRepository.getSingleNetworkYield( + token0Address: token0Address, + token1Address: token1Address, + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + searchSettings: any(named: "searchSettings"), + network: any(named: "network"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).called(1); + }); + + test( + """When calling `getBestPools` with group ids, it should call the yield repository to get the best pools + passing the group ids""", + () async { + when( + () => yieldRepository.getSingleNetworkYield( + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + network: any(named: "network"), + ), + ).thenAnswer((_) async => YieldsDto.fixture()); + + const group0Id = "group0"; + const group1Id = "group1"; + + await sut.getBestPools(token0AddressOrId: null, token1AddressOrId: null, group0Id: group0Id, group1Id: group1Id); + + verify( + () => yieldRepository.getSingleNetworkYield( + token0Address: null, + token1Address: null, + group0Id: group0Id, + group1Id: group1Id, + searchSettings: any(named: "searchSettings"), + network: any(named: "network"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).called(1); + }, + ); + + test( + """When calling `getBestPools` with group ids and token addresses, it should call the yield repository to get the best pools + passing both the token addresses and the group ids""", + () async { + when( + () => yieldRepository.getSingleNetworkYield( + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + network: any(named: "network"), + ), + ).thenAnswer((_) async => YieldsDto.fixture()); + + const group0Id = "group0"; + const group1Id = "group1"; + + const token0Address = "token0Address"; + const token1Address = "token1Address"; + + await sut.getBestPools( + token0AddressOrId: token0Address, + token1AddressOrId: token1Address, + group0Id: group0Id, + group1Id: group1Id, + ); - verify(() => yieldRepository.getSingleNetworkYield( + verify( + () => yieldRepository.getSingleNetworkYield( token0Address: token0Address, token1Address: token1Address, + group0Id: group0Id, + group1Id: group1Id, searchSettings: any(named: "searchSettings"), network: any(named: "network"), blockedProtocolIds: any(named: "blockedProtocolIds"), - )).called(1); - }); + ), + ).called(1); + }, + ); + + test( + """When calling `getBestPools` with group ids, and the network is all networks, + it should call the yield repository to get the best pools for all networks + passing the group ids""", + () async { + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + + when( + () => yieldRepository.getAllNetworksYield( + token0InternalId: any(named: "token0InternalId"), + token1InternalId: any(named: "token1InternalId"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ), + ).thenAnswer((_) async => YieldsDto.fixture()); + + const group0Id = "group0"; + const group1Id = "group1"; + + await sut.getBestPools(token0AddressOrId: null, token1AddressOrId: null, group0Id: group0Id, group1Id: group1Id); + + verify( + () => yieldRepository.getAllNetworksYield( + token0InternalId: null, + token1InternalId: null, + group0Id: group0Id, + group1Id: group1Id, + searchSettings: any(named: "searchSettings"), + + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).called(1); + }, + ); - test("""When calling `getBestPools` and receiving an empty list of pools, + test( + """When calling `getBestPools` with group ids and token addresses at all networks, + it should call the yield repository to get the best pools passing both the token + addresses and the group ids to get pools for all networks""", + () async { + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + + when( + () => yieldRepository.getAllNetworksYield( + token0InternalId: any(named: "token0InternalId"), + token1InternalId: any(named: "token1InternalId"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ), + ).thenAnswer((_) async => YieldsDto.fixture()); + + const group0Id = "group0"; + const group1Id = "group1"; + const token0Address = "token0Address"; + const token1Address = "token1Address"; + + await sut.getBestPools( + token0AddressOrId: token0Address, + token1AddressOrId: token1Address, + group0Id: group0Id, + group1Id: group1Id, + ); + + verify( + () => yieldRepository.getAllNetworksYield( + token0InternalId: token0Address, + token1InternalId: token1Address, + group0Id: group0Id, + group1Id: group1Id, + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).called(1); + }, + ); + + test( + """When calling `getBestPools` and receiving an empty list of pools, it should emit the noYields state with the min liquidity searched returned - from the repository""", () async { - const minLiquidityUSD = 123; + from the repository""", + () async { + const minLiquidityUSD = 123; - when(() => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - searchSettings: any(named: "searchSettings"), - network: any(named: "network"))).thenAnswer( - (_) async => const YieldsDto(pools: [], filters: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD)), - ); + when( + () => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + searchSettings: any(named: "searchSettings"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + network: any(named: "network"), + ), + ).thenAnswer( + (_) async => const YieldsDto( + pools: [], + filters: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD), + ), + ); - expectLater( + expectLater( sut.stream, emitsInOrder([ const DepositState.loading(), - const DepositState.noYields( - filtersApplied: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD), - ), - ])); + const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD)), + ]), + ); - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: ""); - }); + await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: null, group1Id: null); + }, + ); test("When calling `getBestPools` and receiving a list of pools it should emit success state", () async { final pools = YieldsDto.fixture(); - when(() => yieldRepository.getSingleNetworkYield( + when( + () => yieldRepository.getSingleNetworkYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), searchSettings: any(named: "searchSettings"), - network: any(named: "network"))).thenAnswer((_) async => pools); + group1Id: any(named: "group1Id"), + group0Id: any(named: "group0Id"), + network: any(named: "network"), + ), + ).thenAnswer((_) async => pools); expectLater(sut.stream, emitsInOrder([const DepositState.loading(), DepositState.success(pools)])); - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: ""); + await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: null, group1Id: null); }); test("When calling `getBestPools` and receiving an error, it should emit the error state", () async { - when(() => yieldRepository.getSingleNetworkYield( + when( + () => yieldRepository.getSingleNetworkYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), searchSettings: any(named: "searchSettings"), - network: any(named: "network"))).thenThrow(Exception()); + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + network: any(named: "network"), + ), + ).thenThrow(Exception()); expectLater(sut.stream, emitsInOrder([const DepositState.loading(), const DepositState.error()])); - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: ""); + await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: null, group1Id: null); }); test("When calling `selectYield` it should save the selected yield in a variable", () async { final selectedYield = YieldDto.fixture(); - await sut.selectYield(selectedYield, YieldTimeFrame.day); + await sut.selectYield(selectedYield); expect(sut.selectedYield, selectedYield); }); @@ -269,19 +468,19 @@ void main() { expectLater(sut.selectedYieldStream, emits(selectedYield)); - await sut.selectYield(selectedYield, YieldTimeFrame.day); + await sut.selectYield(selectedYield); }); test("When calling `selectYield` with a non-empty yield it should get the pool tick", () async { final selectedYield = YieldDto.fixture(); - await sut.selectYield(selectedYield, YieldTimeFrame.day); + await sut.selectYield(selectedYield); verify(() => poolService.getPoolTick(selectedYield)).called(1); }); test("When calling `selectYield` but the yield is null, it should not get the pool tick", () async { - await sut.selectYield(null, null); + await sut.selectYield(null); verifyNever(() => uniswapV3PoolImpl.slot0()); }); @@ -289,62 +488,66 @@ void main() { test("When calling `getSelectedPoolTick` it should set the latest pool tick to null", () async { expectLater(sut.latestPoolTick, null); - await sut.selectYield(YieldDto.fixture(), YieldTimeFrame.day); + await sut.selectYield(YieldDto.fixture()); await sut.getSelectedPoolTick(); }); test("When calling `getSelectedPoolTick` it should use the pool service to get it", () async { final yieldDto = YieldDto.fixture(); - await sut.selectYield(yieldDto, YieldTimeFrame.day); + await sut.selectYield(yieldDto); await sut.getSelectedPoolTick(); verify(() => poolService.getPoolTick(yieldDto)).called(1); }); - test("""" + test( + """" When calling `getSelectedPoolTick` for a selected pool, but when the call to the contract completes, the selected pool is not the same as the one passed to the call, it shoul re-call - `getSelectedPoolTick` to get the correct pool tick""", () async { - final expectedYieldBTick = BigInt.from(326287637265372111); + `getSelectedPoolTick` to get the correct pool tick""", + () async { + final expectedYieldBTick = BigInt.from(326287637265372111); - const yieldAPoolAddress = "0x3263782637263"; - const yieldBPoolAddress = "0xPoolAddressYieldB"; + const yieldAPoolAddress = "0x3263782637263"; + const yieldBPoolAddress = "0xPoolAddressYieldB"; - final yieldA = YieldDto.fixture().copyWith(poolAddress: yieldAPoolAddress); - final yieldB = YieldDto.fixture().copyWith(poolAddress: yieldBPoolAddress); + final yieldA = YieldDto.fixture().copyWith(poolAddress: yieldAPoolAddress); + final yieldB = YieldDto.fixture().copyWith(poolAddress: yieldBPoolAddress); - when(() => poolService.getPoolTick(any())).thenAnswer((_) async { when(() => poolService.getPoolTick(any())).thenAnswer((_) async { - return expectedYieldBTick; - }); + when(() => poolService.getPoolTick(any())).thenAnswer((_) async { + return expectedYieldBTick; + }); - await sut.selectYield(yieldB, YieldTimeFrame.day); + await sut.selectYield(yieldB); - return poolTick; - }); + return poolTick; + }); - await sut.selectYield(yieldA, YieldTimeFrame.day); // assuming that select yield will call `getSelectedPoolTick` + await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolTick` - verify(() => poolService.getPoolTick(yieldB)) - .called(1); // 2 because of the check in the `getSelectedPoolTick` that will re-call, and the selection + verify( + () => poolService.getPoolTick(yieldB), + ).called(1); // 2 because of the check in the `getSelectedPoolTick` that will re-call, and the selection - expect(sut.latestPoolTick, expectedYieldBTick); - }); + expect(sut.latestPoolTick, expectedYieldBTick); + }, + ); - test("""When calling `selectYield` it should first emit the selected yield latest tick from the DTO - (without making a contract call)""", () { - final latestTickYield = BigInt.from(27189); + test( + """When calling `selectYield` it should first emit the selected yield latest tick from the DTO + (without making a contract call)""", + () { + final latestTickYield = BigInt.from(27189); - expectLater(sut.poolTickStream, emits(latestTickYield)); + expectLater(sut.poolTickStream, emits(latestTickYield)); - sut.selectYield( - YieldDto.fixture().copyWith(latestTick: latestTickYield.toString()), - YieldTimeFrame.day, - ); + sut.selectYield(YieldDto.fixture().copyWith(latestTick: latestTickYield.toString())); - expect(sut.latestPoolTick, latestTickYield); - }); + expect(sut.latestPoolTick, latestTickYield); + }, + ); test( """When calling 'getSelectedPoolTick' with `forceRefresh` true, @@ -353,7 +556,7 @@ void main() { final selectedYield = YieldDto.fixture(); when(() => poolService.getPoolTick(any())).thenAnswer((_) async => poolTick); - await sut.selectYield(selectedYield, YieldTimeFrame.day); + await sut.selectYield(selectedYield); await sut.getSelectedPoolTick(forceRefresh: true); verify(() => poolService.getPoolTick(selectedYield)).called(2); @@ -368,7 +571,7 @@ void main() { final selectedYield = YieldDto.fixture(); when(() => poolService.getPoolTick(any())).thenAnswer((_) async => poolTick); - await sut.selectYield(selectedYield, YieldTimeFrame.day); + await sut.selectYield(selectedYield); await sut.getSelectedPoolTick(); await sut.getSelectedPoolTick(); @@ -391,9 +594,9 @@ void main() { when(() => poolService.getPoolTick(any())).thenAnswer((_) async => poolTick); - await sut.selectYield(yieldA, YieldTimeFrame.day); // assuming that select yield will call `getSelectedPoolTick` - await sut.selectYield(yieldB, YieldTimeFrame.day); - await sut.selectYield(yieldC, YieldTimeFrame.day); + await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolTick` + await sut.selectYield(yieldB); + await sut.selectYield(yieldC); verify(() => poolService.getPoolTick(yieldA)).called(1); verify(() => poolService.getPoolTick(yieldB)).called(1); @@ -421,9 +624,9 @@ void main() { when(() => poolService.getPoolTick(any())).thenAnswer((_) async => poolTick); - await sut.selectYield(yieldA, YieldTimeFrame.day); // assuming that select yield will call `getSelectedPoolTick` - await sut.selectYield(yieldB, YieldTimeFrame.day); - await sut.selectYield(yieldC, YieldTimeFrame.day); + await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolTick` + await sut.selectYield(yieldB); + await sut.selectYield(yieldC); verify(() => poolService.getPoolTick(yieldA)).called(1); verify(() => poolService.getPoolTick(yieldB)).called(1); @@ -437,24 +640,15 @@ void main() { one and emit it""", () async { const poolAddress = "0xPoolAddress"; - final yieldA = YieldDto.fixture().copyWith( - poolAddress: poolAddress, - chainId: AppNetworks.mainnet.chainId, - ); - final yieldB = YieldDto.fixture().copyWith( - poolAddress: poolAddress, - chainId: AppNetworks.sepolia.chainId, - ); - final yieldC = YieldDto.fixture().copyWith( - poolAddress: poolAddress, - chainId: AppNetworks.unichain.chainId, - ); + final yieldA = YieldDto.fixture().copyWith(poolAddress: poolAddress, chainId: AppNetworks.mainnet.chainId); + final yieldB = YieldDto.fixture().copyWith(poolAddress: poolAddress, chainId: AppNetworks.sepolia.chainId); + final yieldC = YieldDto.fixture().copyWith(poolAddress: poolAddress, chainId: AppNetworks.unichain.chainId); when(() => poolService.getPoolTick(any())).thenAnswer((_) async => poolTick); - await sut.selectYield(yieldA, YieldTimeFrame.day); // assuming that select yield will call `getSelectedPoolTick` - await sut.selectYield(yieldB, YieldTimeFrame.day); - await sut.selectYield(yieldC, YieldTimeFrame.day); + await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolTick` + await sut.selectYield(yieldB); + await sut.selectYield(yieldC); verify(() => poolService.getPoolTick(yieldA)).called(1); verify(() => poolService.getPoolTick(yieldB)).called(1); @@ -472,49 +666,54 @@ void main() { sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, appCubit, zupAnalytics, poolService); when(() => poolService.getPoolTick(any())).thenAnswer((_) async => poolTick); when(() => zupSingletonCache.clear()).thenAnswer((_) async => {}); - when(() => zupSingletonCache.run( - any(), - key: any(named: "key"), - expiration: any(named: "expiration"), - ignoreCache: any(named: "ignoreCache"), - )).thenAnswer((_) async => poolTick); - - await sut.selectYield(selectedYield, YieldTimeFrame.day); + when( + () => zupSingletonCache.run( + any(), + key: any(named: "key"), + expiration: any(named: "expiration"), + ignoreCache: any(named: "ignoreCache"), + ), + ).thenAnswer((_) async => poolTick); + + await sut.selectYield(selectedYield); await sut.getSelectedPoolTick(); - verify(() => zupSingletonCache.run( - any(), - key: "poolTick-${selectedYield.poolAddress}-${selectedYield.network.name}", - expiration: const Duration(seconds: 30 - 1), - ignoreCache: false, - )).called(1); + verify( + () => zupSingletonCache.run( + any(), + key: "poolTick-${selectedYield.poolAddress}-${selectedYield.network.name}", + expiration: const Duration(seconds: 30 - 1), + ignoreCache: false, + ), + ).called(1); }, ); - test("""When calling `getSelectedPoolTick` + test( + """When calling `getSelectedPoolTick` it should emit the pool tick got from the contract, after emitting the one - from the yield call""", () async { - final newExpectedPoolTick = BigInt.from(97866745634534392); - final latestTickYield = BigInt.from(27189); + from the yield call""", + () async { + final newExpectedPoolTick = BigInt.from(97866745634534392); + final latestTickYield = BigInt.from(27189); - when(() => poolService.getPoolTick(any())).thenAnswer((_) async => newExpectedPoolTick); + when(() => poolService.getPoolTick(any())).thenAnswer((_) async => newExpectedPoolTick); - expectLater(sut.poolTickStream, emitsInOrder([latestTickYield, newExpectedPoolTick])); + expectLater(sut.poolTickStream, emitsInOrder([latestTickYield, newExpectedPoolTick])); - await sut.selectYield( - YieldDto.fixture().copyWith(latestTick: latestTickYield.toString()), - YieldTimeFrame.day, - ); // assuming that select yield will call `getSelectedPoolTick` - }); + await sut.selectYield( + YieldDto.fixture().copyWith(latestTick: latestTickYield.toString()), + ); // assuming that select yield will call `getSelectedPoolTick` + }, + ); test("When calling `getSelectedPoolTick` it should save the pool tick in the cubit", () async { final expectedPoolTick = BigInt.from(97866745634534392); when(() => poolService.getPoolTick(any())).thenAnswer((_) async => expectedPoolTick); - await sut.selectYield( - YieldDto.fixture(), YieldTimeFrame.day); // assuming that select yield will call `getSelectedPoolTick` + await sut.selectYield(YieldDto.fixture()); // assuming that select yield will call `getSelectedPoolTick` expect(sut.latestPoolTick, expectedPoolTick); }); @@ -529,29 +728,22 @@ void main() { await sut.selectYield( YieldDto.fixture().copyWith(latestTick: yieldTick.toString()), - YieldTimeFrame.day, ); // assuming that select yield will call `getSelectedPoolTick` expect(sut.latestPoolTick, newWxpectedPoolTick); }); test("when closing the cubit, it should close the pool tick stream", () async { - await sut.selectYield(YieldDto.fixture(), YieldTimeFrame.day); + await sut.selectYield(YieldDto.fixture()); await sut.close(); - expect( - () async => await sut.getSelectedPoolTick(), - throwsA(isA()), - ); + expect(() async => await sut.getSelectedPoolTick(), throwsA(isA())); }); test("When closing the cubit, it should close the selected yield stream", () async { await sut.close(); - expect( - () async => await sut.selectYield(YieldDto.fixture(), YieldTimeFrame.day), - throwsA(isA()), - ); + expect(() async => await sut.selectYield(YieldDto.fixture()), throwsA(isA())); }); test("When calling `getWalletTokenAmount` and there's no connected signer, it should return 0", () async { @@ -560,130 +752,132 @@ void main() { expect(tokenAmount, 0); }); - test("When calling `getWalletTokenAmount` and there's a connected signer it should get the wallet token amount", - () async { - final signer = SignerMock(); - const tokenAddress = "0x0"; - const network = AppNetworks.sepolia; - const expectedTokenBalance = 1243.542; + test( + "When calling `getWalletTokenAmount` and there's a connected signer it should get the wallet token amount", + () async { + final signer = SignerMock(); + const tokenAddress = "0x0"; + const network = AppNetworks.sepolia; + const expectedTokenBalance = 1243.542; - when(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) async => 1243.542); - when(() => wallet.signer).thenReturn(signer); - when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); + when( + () => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) async => 1243.542); + when(() => wallet.signer).thenReturn(signer); + when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); - final actualTokenBalance = await sut.getWalletTokenAmount(tokenAddress, network: network); + final actualTokenBalance = await sut.getWalletTokenAmount(tokenAddress, network: network); - expect(actualTokenBalance, expectedTokenBalance); - verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: network.rpcUrl)).called(1); - }); + expect(actualTokenBalance, expectedTokenBalance); + verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: network.rpcUrl)).called(1); + }, + ); test( - "When calling `getWalletTokenAmount` it should use zup singleton cache to return the cached value if the cache is not more than 10 minutes old", - () async { - const tokenAddress = "0x0"; - final signer = SignerMock(); - const network = AppNetworks.sepolia; - const expectedTokenBalance = 1243.542; - const notExpectedTokenBalance = 498361387.42; + "When calling `getWalletTokenAmount` it should use zup singleton cache to return the cached value if the cache is not more than 10 minutes old", + () async { + const tokenAddress = "0x0"; + final signer = SignerMock(); + const network = AppNetworks.sepolia; + const expectedTokenBalance = 1243.542; + const notExpectedTokenBalance = 498361387.42; - when(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) async => 1243.542); - when(() => wallet.signer).thenReturn(signer); - when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); + when( + () => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) async => 1243.542); + when(() => wallet.signer).thenReturn(signer); + when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); - final actualTokenBalance1 = await sut.getWalletTokenAmount(tokenAddress, network: network); + final actualTokenBalance1 = await sut.getWalletTokenAmount(tokenAddress, network: network); - when(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).thenAnswer( - (_) async => notExpectedTokenBalance, - ); + when( + () => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) async => notExpectedTokenBalance); - final actualTokenBalance2 = await sut.getWalletTokenAmount(tokenAddress, network: network); + final actualTokenBalance2 = await sut.getWalletTokenAmount(tokenAddress, network: network); - verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: network.rpcUrl)).called(1); + verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: network.rpcUrl)).called(1); - expect(actualTokenBalance1, expectedTokenBalance); - expect(actualTokenBalance2, expectedTokenBalance); - }); + expect(actualTokenBalance1, expectedTokenBalance); + expect(actualTokenBalance2, expectedTokenBalance); + }, + ); - test("When calling `getWalletTokenAmount` it should use zup singleton cache with a 10 minutes expiration time", - () async { - const tokenAddress = "0x0"; - final signer = SignerMock(); - const network = AppNetworks.sepolia; - const expectedTokenBalance = 1243.542; - const notExpectedTokenBalance = 498361387.42; + test( + "When calling `getWalletTokenAmount` it should use zup singleton cache with a 10 minutes expiration time", + () async { + const tokenAddress = "0x0"; + final signer = SignerMock(); + const network = AppNetworks.sepolia; + const expectedTokenBalance = 1243.542; + const notExpectedTokenBalance = 498361387.42; - when(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).thenAnswer( - (_) async => notExpectedTokenBalance, - ); - when(() => wallet.signer).thenReturn(signer); - when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); + when( + () => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) async => notExpectedTokenBalance); + when(() => wallet.signer).thenReturn(signer); + when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); - await sut.getWalletTokenAmount(tokenAddress, network: network); + await sut.getWalletTokenAmount(tokenAddress, network: network); - await withClock(Clock(() => DateTime.now().add(const Duration(minutes: 11))), () async { - when(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) async => expectedTokenBalance); + await withClock(Clock(() => DateTime.now().add(const Duration(minutes: 11))), () async { + when( + () => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) async => expectedTokenBalance); - final actualTokenBalance2 = await sut.getWalletTokenAmount(tokenAddress, network: network); + final actualTokenBalance2 = await sut.getWalletTokenAmount(tokenAddress, network: network); - verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: network.rpcUrl)) - .called(2); // it should call the method twice because the cache is expired + verify( + () => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: network.rpcUrl), + ).called(2); // it should call the method twice because the cache is expired - expect(actualTokenBalance2, expectedTokenBalance); - }); - }); + expect(actualTokenBalance2, expectedTokenBalance); + }); + }, + ); - test("When calling `getWalletTokenAmount` and an error occurs getting the wallet balance, it should return 0", - () async { - final signer = SignerMock(); - const tokenAddress = "0x0"; - const network = AppNetworks.sepolia; + test( + "When calling `getWalletTokenAmount` and an error occurs getting the wallet balance, it should return 0", + () async { + final signer = SignerMock(); + const tokenAddress = "0x0"; + const network = AppNetworks.sepolia; - when(() => wallet.tokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).thenThrow(Exception()); - when(() => wallet.signer).thenReturn(signer); - when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); + when(() => wallet.tokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).thenThrow(Exception()); + when(() => wallet.signer).thenReturn(signer); + when(() => signer.address).thenAnswer((_) async => "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"); - final actualTokenBalance = await sut.getWalletTokenAmount(tokenAddress, network: network); + final actualTokenBalance = await sut.getWalletTokenAmount(tokenAddress, network: network); - expect(actualTokenBalance, 0.0); - }); + expect(actualTokenBalance, 0.0); + }, + ); - test( - "When calling `saveDepositSettings` it should save the passed params in the cache", - () async { - when(() => cache.saveDepositSettings(any())).thenAnswer((_) async => () {}); + test("When calling `saveDepositSettings` it should save the passed params in the cache", () async { + when(() => cache.saveDepositSettings(any())).thenAnswer((_) async => () {}); - const slippage = Slippage.zeroPointOnePercent; - const deadline = Duration(minutes: 5); + const slippage = Slippage.zeroPointOnePercent; + const deadline = Duration(minutes: 5); - final expectedDepositSettings = DepositSettingsDto( - deadlineMinutes: deadline.inMinutes, - maxSlippage: slippage.value.toDouble(), - ); + final expectedDepositSettings = DepositSettingsDto( + deadlineMinutes: deadline.inMinutes, + maxSlippage: slippage.value.toDouble(), + ); - await sut.saveDepositSettings(slippage, deadline); + await sut.saveDepositSettings(slippage, deadline); - verify(() => cache.saveDepositSettings(expectedDepositSettings)).called(1); - }, - ); + verify(() => cache.saveDepositSettings(expectedDepositSettings)).called(1); + }); - test( - "When calling `depositSettings` it should get the deposit settings from the cache", - () { - final expectedDepositSettings = DepositSettingsDto( - deadlineMinutes: 5, - maxSlippage: 0.01, - ); + test("When calling `depositSettings` it should get the deposit settings from the cache", () { + const expectedDepositSettings = DepositSettingsDto(deadlineMinutes: 5, maxSlippage: 0.01); - when(() => cache.getDepositSettings()).thenReturn(expectedDepositSettings); + when(() => cache.getDepositSettings()).thenReturn(expectedDepositSettings); - final actualDepositSettings = sut.depositSettings; + final actualDepositSettings = sut.depositSettings; - expect(actualDepositSettings, expectedDepositSettings); - }, - ); + expect(actualDepositSettings, expectedDepositSettings); + }); test("When calling 'poolSearchSettings' it should get the pool search settings from the cache", () { final expectedPoolSearchSettings = PoolSearchSettingsDto(minLiquidityUSD: 129816); @@ -694,149 +888,224 @@ void main() { expect(actualPoolSearchSettings, expectedPoolSearchSettings); }); - test("""When calling 'getBestPools' with the param 'ignoreMinLiquidity' true, - it should pass the minLiquidityUSD as 0 to the repository""", () async { - when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); + test( + """When calling 'getBestPools' with the param 'ignoreMinLiquidity' true, + it should pass the minLiquidityUSD as 0 to the repository""", + () async { + when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); - when(() => yieldRepository.getSingleNetworkYield( + when( + () => yieldRepository.getSingleNetworkYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), network: any(named: "network"), searchSettings: any(named: "searchSettings"), - )).thenAnswer((_) async => YieldsDto.fixture()); - - await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", ignoreMinLiquidity: true); + ), + ).thenAnswer((_) async => YieldsDto.fixture()); + + await sut.getBestPools( + token0AddressOrId: "0x", + token1AddressOrId: "0x", + group0Id: null, + group1Id: null, + ignoreMinLiquidity: true, + ); - verify(() => yieldRepository.getSingleNetworkYield( + verify( + () => yieldRepository.getSingleNetworkYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), network: any(named: "network"), searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: 0), - )).called(1); - }); + ), + ).called(1); + }, + ); - test("""When calling 'getBestPools' with the current network as all networks and the param - 'ignoreMinLiquidity' true, it should pass the minLiquidityUSD as 0 to the repository""", () async { - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); - when(() => yieldRepository.getAllNetworksYield( + test( + """When calling 'getBestPools' with the current network as all networks and the param + 'ignoreMinLiquidity' true, it should pass the minLiquidityUSD as 0 to the repository""", + () async { + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); + when( + () => yieldRepository.getAllNetworksYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0InternalId: any(named: "token0InternalId"), token1InternalId: any(named: "token1InternalId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), searchSettings: any(named: "searchSettings"), - )).thenAnswer((_) async => YieldsDto.fixture()); - - await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", ignoreMinLiquidity: true); + ), + ).thenAnswer((_) async => YieldsDto.fixture()); + + await sut.getBestPools( + token0AddressOrId: "0x", + token1AddressOrId: "0x", + ignoreMinLiquidity: true, + group0Id: null, + group1Id: null, + ); - verify(() => yieldRepository.getAllNetworksYield( + verify( + () => yieldRepository.getAllNetworksYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0InternalId: any(named: "token0InternalId"), token1InternalId: any(named: "token1InternalId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: 0), - )).called(1); - }); + ), + ).called(1); + }, + ); - test("""When calling 'getBestPools' with the param 'ignoreMinLiquidity' false, - it should pass the minLiquidityUSD as the saved value to the repository""", () async { - const minLiquiditySaved = 129816; + test( + """When calling 'getBestPools' with the param 'ignoreMinLiquidity' false, + it should pass the minLiquidityUSD as the saved value to the repository""", + () async { + const minLiquiditySaved = 129816; - when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: minLiquiditySaved)); - when(() => yieldRepository.getSingleNetworkYield( + when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: minLiquiditySaved)); + when( + () => yieldRepository.getSingleNetworkYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), network: any(named: "network"), searchSettings: any(named: "searchSettings"), - )).thenAnswer((_) async => YieldsDto.fixture()); - - await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", ignoreMinLiquidity: false); + ), + ).thenAnswer((_) async => YieldsDto.fixture()); + + await sut.getBestPools( + token0AddressOrId: "0x", + token1AddressOrId: "0x", + ignoreMinLiquidity: false, + group0Id: null, + group1Id: null, + ); - verify(() => yieldRepository.getSingleNetworkYield( + verify( + () => yieldRepository.getSingleNetworkYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), network: any(named: "network"), searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minLiquiditySaved), - )).called(1); - }); + ), + ).called(1); + }, + ); - test( - "when calling `getBestPools` it should log the search on analytics with the correct events", - () { - const token0Address = "0x123"; - const token1Address = "0x456"; - const network = AppNetworks.sepolia; + test("when calling `getBestPools` it should log the search on analytics with the correct events", () { + const token0Address = "0x123"; + const token1Address = "0x456"; + const network = AppNetworks.sepolia; - when(() => appCubit.selectedNetwork).thenReturn(network); + when(() => appCubit.selectedNetwork).thenReturn(network); - sut.getBestPools(token0AddressOrId: token0Address, token1AddressOrId: token1Address); + sut.getBestPools( + token0AddressOrId: token0Address, + token1AddressOrId: token1Address, + group0Id: null, + group1Id: null, + ); - verify(() => zupAnalytics.logSearch(token0: token0Address, token1: token1Address, network: network.label)) - .called(1); - }, - ); + verify( + () => zupAnalytics.logSearch( + token0: token0Address, + token1: token1Address, + network: network.label, + group0: null, + group1: null, + ), + ).called(1); + }); test( - "When calling `getBestPools` and the network is all networks, it should call the endpoint to search in all networks", - () async { - const token0Address = "0x123"; - const token1Address = "0x456"; + "When calling `getBestPools` and the network is all networks, it should call the endpoint to search in all networks", + () async { + const token0Address = "0x123"; + const token1Address = "0x456"; - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - await sut.getBestPools(token0AddressOrId: token0Address, token1AddressOrId: token1Address); + await sut.getBestPools( + token0AddressOrId: token0Address, + token1AddressOrId: token1Address, + group0Id: null, + group1Id: null, + ); - verify(() => yieldRepository.getAllNetworksYield( + verify( + () => yieldRepository.getAllNetworksYield( blockedProtocolIds: any(named: "blockedProtocolIds"), token0InternalId: token0Address, token1InternalId: token1Address, + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), searchSettings: any(named: "searchSettings"), - )).called(1); - }); - - test("When calling 'selectYield' it should update the selected time frame as well to the one passed", () async { - final selectedYield = YieldDto.fixture(); - const selectedYieldTimeFrame = YieldTimeFrame.day; - - await sut.selectYield(selectedYield, selectedYieldTimeFrame); - - expect(sut.selectedYieldTimeframe, selectedYieldTimeFrame); - }); + ), + ).called(1); + }, + ); - test("""When calling 'getBestPools' and all networks is the selected network, + test( + """When calling 'getBestPools' and all networks is the selected network, it should call the repository to get it passing the blocked protocol ids got - from the cache""", () async { - final cachedBlockedProtocolIds = ["0x1", "0x2", "ababa"]; - when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + from the cache""", + () async { + final cachedBlockedProtocolIds = ["0x1", "0x2", "ababa"]; + when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x"); + await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", group0Id: null, group1Id: null); - verify(() => yieldRepository.getAllNetworksYield( + verify( + () => yieldRepository.getAllNetworksYield( blockedProtocolIds: cachedBlockedProtocolIds, token0InternalId: any(named: "token0InternalId"), token1InternalId: any(named: "token1InternalId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), searchSettings: any(named: "searchSettings"), - )).called(1); - }); + ), + ).called(1); + }, + ); - test("""When calling 'getBestPools' and all networks is not the selected network, + test( + """When calling 'getBestPools' and all networks is not the selected network, it should call the repository to get it passing the blocked protocol ids got - from the cache""", () async { - final cachedBlockedProtocolIds = ["017628761", "asaas", "ababa"]; - when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.sepolia); + from the cache""", + () async { + final cachedBlockedProtocolIds = ["017628761", "asaas", "ababa"]; + when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.sepolia); - await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x"); + await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", group0Id: null, group1Id: null); - verify(() => yieldRepository.getSingleNetworkYield( + verify( + () => yieldRepository.getSingleNetworkYield( blockedProtocolIds: cachedBlockedProtocolIds, network: any(named: "network"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), searchSettings: any(named: "searchSettings"), - )).called(1); - }); + ), + ).called(1); + }, + ); } diff --git a/test/app/create/deposit/deposit_page_test.dart b/test/app/create/deposit/deposit_page_test.dart index 43b85ea..a60a0ae 100644 --- a/test/app/create/deposit/deposit_page_test.dart +++ b/test/app/create/deposit/deposit_page_test.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -20,11 +21,12 @@ import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; import 'package:zup_app/core/dtos/token_price_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; -import 'package:zup_app/core/enums/zup_navigator_paths.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/core/pool_service.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; @@ -32,6 +34,7 @@ import 'package:zup_app/core/slippage.dart'; import 'package:zup_app/core/zup_analytics.dart'; import 'package:zup_app/core/zup_links.dart'; import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; import 'package:zup_app/gen/assets.gen.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; import 'package:zup_core/zup_core.dart'; @@ -83,6 +86,10 @@ void main() { () => Assets.lotties.empty.lottie(animate: false), instanceName: InjectInstanceNames.lottieEmpty, ); + inject.registerFactory( + () => Assets.lotties.numbers.lottie(animate: false), + instanceName: InjectInstanceNames.lottieNumbers, + ); inject.registerFactory( () => Assets.lotties.radar.lottie(animate: false), instanceName: InjectInstanceNames.lottieRadar, @@ -100,6 +107,7 @@ void main() { instanceName: InjectInstanceNames.appScrollController, ); + inject.registerFactory(() => false, instanceName: InjectInstanceNames.infinityAnimationAutoPlay); inject.registerFactory(() => cache); inject.registerFactory(() => ZupAnalyticsMock()); inject.registerFactory(() => ZupLinksMock()); @@ -121,11 +129,15 @@ void main() { when(() => tokensRepository.getTokenPrice(any(), any())).thenAnswer((_) async => TokenPriceDto.fixture()); when(() => cubit.stream).thenAnswer((_) => const Stream.empty()); when(() => cubit.state).thenAnswer((_) => const DepositState.initial()); - when(() => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), - )).thenAnswer((_) async {}); + when( + () => cubit.getBestPools( + token0AddressOrId: any(named: "token0AddressOrId"), + token1AddressOrId: any(named: "token1AddressOrId"), + ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ), + ).thenAnswer((_) async {}); when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto.fixture()); when(() => cubit.selectedYieldStream).thenAnswer((_) => const Stream.empty()); when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.sepolia); @@ -136,7 +148,6 @@ void main() { when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => ()); when(() => cubit.depositSettings).thenReturn(DepositSettingsDto.fixture()); when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto.fixture()); - when(() => cubit.selectedYieldTimeframe).thenReturn(YieldTimeFrame.day); }); tearDown(() async { @@ -145,12 +156,9 @@ void main() { }); Future goldenBuilder({bool isMobile = false}) => goldenDeviceBuilder( - BlocProvider.value( - value: cubit, - child: const DepositPage(), - ), - device: isMobile ? GoldenDevice.mobile : GoldenDevice.pc, - ); + BlocProvider.value(value: cubit, child: const DepositPage()), + device: isMobile ? GoldenDevice.mobile : GoldenDevice.pc, + ); zGoldenTest("When initializing the page it should call setup in the cubit", (tester) async { await tester.runAsync(() async { @@ -161,63 +169,75 @@ void main() { verify(() => cubit.setup()).called(1); }); - zGoldenTest("""When initializing the page it should get the list of best pools, - passing the correct token addresses (from the url)""", (tester) async { - const token0Address = "0xToken0"; - const token1Address = "0xToken1"; + zGoldenTest( + """When initializing the page it should get the list of best pools, + passing the correct token addresses and group ids (from the url)""", + (tester) async { + const token0Address = "0xToken0"; + const token1Address = "0xToken1"; + const group0Id = "0xGroup0"; + const group1Id = "0xGroup1"; - when(() => navigator.getParam(ZupNavigatorPaths.deposit.routeParamsName!.param0)).thenReturn(token0Address); - when(() => navigator.getParam(ZupNavigatorPaths.deposit.routeParamsName!.param1)).thenReturn(token1Address); + when(() => navigator.getParam(ZupDepositRouteParamsNames().group0)).thenReturn(group0Id); + when(() => navigator.getParam(ZupDepositRouteParamsNames().group1)).thenReturn(group1Id); + when(() => navigator.getParam(ZupDepositRouteParamsNames().token0)).thenReturn(token0Address); + when(() => navigator.getParam(ZupDepositRouteParamsNames().token1)).thenReturn(token1Address); - await tester.runAsync(() async { - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - }); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - verify( - () => cubit.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - ), - ).called(1); - }); + verify( + () => cubit.getBestPools( + token0AddressOrId: token0Address, + token1AddressOrId: token1Address, + group0Id: group0Id, + group1Id: group1Id, + ignoreMinLiquidity: false, + ), + ).called(1); + }); + }, + ); - zGoldenTest("When the cubit state is loading it should show the loading state", - goldenFileName: "deposit_page_loading", (tester) async { - when(() => cubit.state).thenReturn(const DepositState.loading()); + zGoldenTest( + "When the cubit state is loading it should show the loading state", + goldenFileName: "deposit_page_loading", + (tester) async { + when(() => cubit.state).thenReturn(const DepositState.loading()); - await tester.runAsync(() async { - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - }); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }, + ); - zGoldenTest("When the cubit state is noYields with no min liquidity searched, it should just show the noYields state", - goldenFileName: "deposit_page_no_yields", (tester) async { - when(() => cubit.state).thenReturn( - const DepositState.noYields( - filtersApplied: PoolSearchFiltersDto(minTvlUsd: 0), - ), - ); + zGoldenTest( + "When the cubit state is noYields with no min liquidity searched, it should just show the noYields state", + goldenFileName: "deposit_page_no_yields", + (tester) async { + when( + () => cubit.state, + ).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 0))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( """When the cubit state is noYields and the search had a min liquidity set, it should show the noYields state with a helper text saying it, and a button to search all pools""", goldenFileName: "deposit_page_no_yields_filtered_by_min_liquidity", (tester) async { - when(() => cubit.state).thenReturn( - const DepositState.noYields( - filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654), - ), - ); + when( + () => cubit.state, + ).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654))); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); @@ -231,16 +251,17 @@ void main() { (tester) async { when( () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity")), + token0AddressOrId: any(named: "token0AddressOrId"), + token1AddressOrId: any(named: "token1AddressOrId"), + ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ), ).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn( - const DepositState.noYields( - filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654), - ), - ); + when( + () => cubit.state, + ).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654))); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); @@ -252,29 +273,35 @@ void main() { () => cubit.getBestPools( token0AddressOrId: any(named: "token0AddressOrId"), token1AddressOrId: any(named: "token1AddressOrId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), ignoreMinLiquidity: true, ), ).called(1); }, ); - zGoldenTest("""When clicking the helper button in the no yields state, - it should navigate back to choose tokens stage""", (tester) async { - when(() => navigator.navigateToNewPosition()).thenAnswer((_) async {}); + zGoldenTest( + """When clicking the helper button in the no yields state, + it should navigate back to choose tokens stage""", + (tester) async { + when(() => navigator.navigateToNewPosition()).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto())); + when(() => cubit.state).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("help-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("help-button"))); + await tester.pumpAndSettle(); - verify(() => navigator.navigateToNewPosition()).called(1); - }); + verify(() => navigator.navigateToNewPosition()).called(1); + }, + ); - zGoldenTest("When the cubit state is error, it should show the error state", goldenFileName: "deposit_page_error", - (tester) async { + zGoldenTest("When the cubit state is error, it should show the error state", goldenFileName: "deposit_page_error", ( + tester, + ) async { when(() => cubit.state).thenReturn(const DepositState.error()); await tester.pumpDeviceBuilder(await goldenBuilder()); @@ -283,152 +310,215 @@ void main() { await tester.pumpAndSettle(); }); - zGoldenTest(""""When clicking the helper button in the error state, - it should try to get best pools again with the same tokens""", (tester) async { - const token0Address = "0xToken0"; - const token1Address = "0xToken1"; + zGoldenTest( + """"When clicking the helper button in the error state, + it should try to get best pools again with the same tokens + and groups""", + (tester) async { + const token0Address = "0xToken0"; + const token1Address = "0xToken1"; + const group0Id = "0xGroup0"; + const group1Id = "0xGroup1"; - when(() => navigator.getParam(ZupNavigatorPaths.deposit.routeParamsName!.param0)).thenReturn(token0Address); - when(() => navigator.getParam(ZupNavigatorPaths.deposit.routeParamsName!.param1)).thenReturn(token1Address); - when(() => cubit.state).thenReturn(const DepositState.error()); + when(() => navigator.getParam(ZupDepositRouteParamsNames().group0)).thenReturn(group0Id); + when(() => navigator.getParam(ZupDepositRouteParamsNames().group1)).thenReturn(group1Id); + when(() => navigator.getParam(ZupDepositRouteParamsNames().token0)).thenReturn(token0Address); + when(() => navigator.getParam(ZupDepositRouteParamsNames().token1)).thenReturn(token1Address); + when(() => cubit.state).thenReturn(const DepositState.error()); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("help-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("help-button"))); + await tester.pumpAndSettle(); - verify(() => cubit.getBestPools(token0AddressOrId: token0Address, token1AddressOrId: token1Address)) - .called(2); // 2 because of the initial call - }); + verify( + () => cubit.getBestPools( + token0AddressOrId: token0Address, + token1AddressOrId: token1Address, + group0Id: group0Id, + group1Id: group1Id, + ), + ).called(2); // 2 because of the initial call + }, + ); - zGoldenTest("When the state is sucess, it should show the success state", goldenFileName: "deposit_page_success", - (tester) async { - final yields = YieldsDto.fixture(); + zGoldenTest("When the state is sucess, it should show the success state", goldenFileName: "deposit_page_success", ( + tester, + ) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture(); - when(() => cubit.state).thenReturn(DepositState.success(yields)); + when(() => cubit.state).thenReturn(DepositState.success(yields)); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); + }); }); - zGoldenTest("""When the state is success, and the minimum liquidity search config is more than 0, + zGoldenTest( + """When the state is success, and the minimum liquidity search config is more than 0, it should show a text about showing only pools with more than X(min) liquidity, and a button - to search all pools""", goldenFileName: "deposit_page_success_filtered_by_min_liquidity", (tester) async { - final yields = YieldsDto.fixture(); - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 97654)); - when(() => cubit.state).thenReturn(DepositState.success(yields)); + to search all pools""", + goldenFileName: "deposit_page_success_filtered_by_min_liquidity", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture(); + when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 97654)); + when(() => cubit.state).thenReturn(DepositState.success(yields)); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When the state is success, and the minimum liquidity search config is 0, + zGoldenTest( + """When the state is success, and the minimum liquidity search config is 0, it should not show a text about showing only pools with more than X(min) liquidity""", - goldenFileName: "deposit_page_success_not_filtered_by_min_liquidity", (tester) async { - final yields = YieldsDto.fixture(); - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 0)); - when(() => cubit.state).thenReturn(DepositState.success(yields)); + goldenFileName: "deposit_page_success_not_filtered_by_min_liquidity", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture(); + when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 0)); + when(() => cubit.state).thenReturn(DepositState.success(yields)); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When the state is success, and the repository returns that the filter for mininum liquidity + zGoldenTest( + """When the state is success, and the repository returns that the filter for mininum liquidity search has zero, but the user has a local filter set, it should show a text and a button to search only pools with the local filter amount set""", - goldenFileName: "deposit_page_success_filtered_by_min_liquidity_local_filter_set", (tester) async { - final yields = YieldsDto.fixture().copyWith( - filters: const PoolSearchFiltersDto(minTvlUsd: 0), - ); // api filter returns 0 - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 2189)); // local filter set - when(() => cubit.state).thenReturn(DepositState.success(yields)); + goldenFileName: "deposit_page_success_filtered_by_min_liquidity_local_filter_set", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture().copyWith( + filters: const PoolSearchFiltersDto(minTvlUsd: 0), + ); // api filter returns 0 + when( + () => cubit.poolSearchSettings, + ).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 2189)); // local filter set + when(() => cubit.state).thenReturn(DepositState.success(yields)); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking in the button to search all pools in the success state + zGoldenTest( + """When clicking in the button to search all pools in the success state that is with a filter for min liquidity, it should call the cubit to get pools with - the ignore min liquidity flag""", (tester) async { - final yields = YieldsDto.fixture().copyWith( - filters: const PoolSearchFiltersDto(minTvlUsd: 12675), - ); + the ignore min liquidity flag""", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture().copyWith(filters: const PoolSearchFiltersDto(minTvlUsd: 12675)); - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); - when(() => cubit.state).thenReturn(DepositState.success(yields)); - when(() => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity"))).thenAnswer((_) async {}); + when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); + when(() => cubit.state).thenReturn(DepositState.success(yields)); + when( + () => cubit.getBestPools( + token0AddressOrId: any(named: "token0AddressOrId"), + token1AddressOrId: any(named: "token1AddressOrId"), + ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ), + ).thenAnswer((_) async {}); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); + await tester.pumpAndSettle(); - verify( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: true, - ), - ).called(1); - }); + verify( + () => cubit.getBestPools( + token0AddressOrId: any(named: "token0AddressOrId"), + token1AddressOrId: any(named: "token1AddressOrId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ignoreMinLiquidity: true, + ), + ).called(1); + }); + }, + ); - zGoldenTest("""When clicking in the button to search only pools with more than x amount in + zGoldenTest( + """When clicking in the button to search only pools with more than x amount in the success state that is without a filter for min liquidity, it should call the cubit to get pools with - the min liquidity set to not be ignored""", (tester) async { - final yields = YieldsDto.fixture().copyWith( - filters: const PoolSearchFiltersDto(minTvlUsd: 0), - ); // api filter returns 0 - - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); // local filter set - when(() => cubit.state).thenReturn(DepositState.success(yields)); - when(() => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity"))).thenAnswer((_) async {}); + the min liquidity set to not be ignored""", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture().copyWith( + filters: const PoolSearchFiltersDto(minTvlUsd: 0), + ); // api filter returns 0 - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + when( + () => cubit.poolSearchSettings, + ).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); // local filter set + when(() => cubit.state).thenReturn(DepositState.success(yields)); + when( + () => cubit.getBestPools( + token0AddressOrId: any(named: "token0AddressOrId"), + token1AddressOrId: any(named: "token1AddressOrId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), + ), + ).thenAnswer((_) async {}); - await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - verify( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: false, - ), - ).called(2); // two calls, one when the page is loaded and one when the user clicks the button - }); + await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); + await tester.pumpAndSettle(); - zGoldenTest("When the state is sucess, and the running device is a mobile, the yield cards should be in a column", - goldenFileName: "deposit_page_success_mobile", (tester) async { - final yields = YieldsDto.fixture(); + verify( + () => cubit.getBestPools( + token0AddressOrId: any(named: "token0AddressOrId"), + token1AddressOrId: any(named: "token1AddressOrId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ignoreMinLiquidity: false, + ), + ).called(2); // two calls, one when the page is loaded and one when the user clicks the button + }); + }, + ); + + zGoldenTest( + "When the state is sucess, and the running device is a mobile, the yield cards should be in a column", + goldenFileName: "deposit_page_success_mobile", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture(); - when(() => cubit.depositSettings).thenReturn(DepositSettingsDto( - deadlineMinutes: 10, - maxSlippage: DepositSettingsDto.defaultMaxSlippage, - )); + when( + () => cubit.depositSettings, + ).thenReturn(const DepositSettingsDto(deadlineMinutes: 10, maxSlippage: DepositSettingsDto.defaultMaxSlippage)); - when(() => cubit.state).thenReturn(DepositState.success(yields)); + when(() => cubit.state).thenReturn(DepositState.success(yields)); - await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); + await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( "When the running device is mobile, the range section should be adapted to it", @@ -438,10 +528,9 @@ void main() { final selectedYield = YieldDto.fixture(); final yields = YieldsDto.fixture(); - when(() => cubit.depositSettings).thenReturn(DepositSettingsDto( - deadlineMinutes: 10, - maxSlippage: DepositSettingsDto.defaultMaxSlippage, - )); + when( + () => cubit.depositSettings, + ).thenReturn(const DepositSettingsDto(deadlineMinutes: 10, maxSlippage: DepositSettingsDto.defaultMaxSlippage)); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); @@ -472,188 +561,193 @@ void main() { verify(() => navigator.navigateToNewPosition()).called(1); }); - zGoldenTest("When hovering the title of the pool time frame section, it should show a tooltip explaining it", - goldenFileName: "deposit_page_timeframe_tooltip", (tester) async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + zGoldenTest( + "When hovering the info icon of the pool time frame section, it should show a tooltip explaining it", + goldenFileName: "deposit_page_timeframe_tooltip", + (tester) async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.hover(find.byKey(const Key("timeframe-tooltip"))); - await tester.pumpAndSettle(); - }); + await tester.hover(find.byKey(const Key("timeframe-tooltip"))); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( - "When clicking learn more in the pool time frame tooltip, it should launch the Zup blog page explaining it", - (tester) async { + "When the selected yield stream in the cubit emits a yield, it should select the yield", + goldenFileName: "deposit_page_selected_yield_stream", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture(); + final selectedYield = yields.poolsSortedBy24hYield.first; + + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(yields)); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest("When selecting a yield, it should call select yield in the cubit", (tester) async { + final yields = YieldsDto.fixture(); + when(() => cubit.selectYield(any())).thenAnswer((_) async {}); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.hover(find.byKey(const Key("timeframe-tooltip"))); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("helper-button-tooltip"))); + await tester.tap(find.byKey(Key("yield-card-${yields.pools.first.poolAddress}")).last); await tester.pumpAndSettle(); - expect( - UrlLauncherPlatformCustomMock.lastLaunchedUrl, - "https://zupprotocol.substack.com/p/zup-timeframes-explained-why-you", - ); + verify(() => cubit.selectYield(any())).called(1); }); - zGoldenTest("When the selected yield stream in the cubit emits a yield, it should select the yield", - goldenFileName: "deposit_page_selected_yield_stream", (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture(); - final selectedYield = yields.best24hYield; + zGoldenTest( + "When selecting a yield, it should scroll down to the range section", + goldenFileName: "deposit_page_select_yield_scroll", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final selectedYieldStreamController = StreamController.broadcast(); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(yields)); + when(() => cubit.selectedYieldStream).thenAnswer((_) => selectedYieldStreamController.stream); + when(() => cubit.selectedYield).thenReturn(null); + when(() => cubit.selectYield(any())).thenAnswer((_) async {}); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("When selecting a yield, it should call select yield in the cubit", (tester) async { - when(() => cubit.selectYield(any(), any())).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("yield-card-24h"))); - await tester.pumpAndSettle(); - - verify(() => cubit.selectYield(any(), any())).called(1); - }); - - zGoldenTest("When selecting a yield, it should scroll down to the range section", - goldenFileName: "deposit_page_select_yield_scroll", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(null); - when(() => cubit.selectYield(any(), any())).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.selectedYield).thenReturn(selectedYield); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + selectedYieldStreamController.add(selectedYield); + when(() => cubit.selectedYield).thenReturn(selectedYield); - await tester.tap(find.byKey(const Key("yield-card-30d"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); + await tester.pumpAndSettle(); - verify(() => cubit.selectYield(any(), any())).called(1); - }); - }); + verify(() => cubit.selectYield(any())).called(1); + }); + }, + ); zGoldenTest( - "When clicking the segmented control to switch the base token to quote token, it should reverse the tokens", - goldenFileName: "deposit_page_reverse_tokens", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + "When clicking the segmented control to switch the base token to quote token, it should reverse the tokens", + goldenFileName: "deposit_page_reverse_tokens", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( - "When clicking the segmented control to switch back to base token, after reversing the tokens, it should reverse again", - goldenFileName: "deposit_page_reverse_tokens_back", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + "When clicking the segmented control to switch back to base token, after reversing the tokens, it should reverse again", + goldenFileName: "deposit_page_reverse_tokens_back", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( - "When clicking the segmented control to switch back to base token, after reversing the tokens, it should reverse again", - goldenFileName: "deposit_page_reverse_tokens_back", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + "When clicking the segmented control to switch back to base token, after reversing the tokens, it should reverse again", + goldenFileName: "deposit_page_reverse_tokens_back", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When emitting an event to the tick stream, - it should calculate the price of the selected yield assets""", goldenFileName: "deposit_page_calculate_price", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + zGoldenTest( + """When emitting an event to the tick stream, + it should calculate the price of the selected yield assets""", + goldenFileName: "deposit_page_calculate_price", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(BigInt.from(174072))); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(BigInt.from(174072))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.pumpAndSettle(); - }); - }); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( - "When reversing the tokens, it should calculate the price based on the reversed tokens, from a given tick in the cubit", - goldenFileName: "deposit_page_calculate_price_reversed", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + "When reversing the tokens, it should calculate the price based on the reversed tokens, from a given tick in the cubit", + goldenFileName: "deposit_page_calculate_price_reversed", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(BigInt.from(174072))); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(BigInt.from(174072))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( "When typing a min price more than the current price, it should show an alert saying that is out of range", goldenFileName: "deposit_page_min_price_out_of_range", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -681,7 +775,7 @@ void main() { goldenFileName: "deposit_page_min_price_out_of_range_reversed_in_range", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -710,7 +804,7 @@ void main() { goldenFileName: "deposit_page_min_price_out_of_range_reversed", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -731,452 +825,501 @@ void main() { }, ); - zGoldenTest("When typing a max price less than the min price, it should show an error message", - goldenFileName: "deposit_page_max_price_less_than_min_price", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + zGoldenTest( + "When typing a max price less than the min price, it should show an error message", + goldenFileName: "deposit_page_max_price_less_than_min_price", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "1000"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "1000"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When typing a max price lower than the current price + zGoldenTest( + """When typing a max price lower than the current price but higher than min price, it shouw show a alert of out of range""", - goldenFileName: "deposit_page_max_price_out_of_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_max_price_out_of_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "0.000000001"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "0.000000001"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "0.0000001"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0.0000001"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("When typing 0 in the max price, it should set it to infinity max price", - goldenFileName: "deposit_page_max_price_set_to_infinity", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + zGoldenTest( + "When typing 0 in the max price, it should set it to infinity max price", + goldenFileName: "deposit_page_max_price_set_to_infinity", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "2"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "2"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), ""); - await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("max-price-selector")), ""); + await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("When typing a min price, but then selecting the full range button, it should set it to 0", - goldenFileName: "deposit_page_min_price_set_to_full_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + zGoldenTest( + "When typing a min price, but then selecting the full range button, it should set it to 0", + goldenFileName: "deposit_page_min_price_set_to_full_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.tap(find.byKey(const Key("full-range-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("full-range-button"))); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( - "When clicking the 5% range button and then clicking the full range button, it should set it to full range", - goldenFileName: "deposit_page_5_percent_set_to_full_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + "When clicking the 5% range button and then clicking the full range button, it should set it to full range", + goldenFileName: "deposit_page_5_percent_set_to_full_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("5-percent-range-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("5-percent-range-button"))); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("full-range-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("full-range-button"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the 5% range button and then clicking the full range button, + zGoldenTest( + """When clicking the 5% range button and then clicking the full range button, it should set it to full range. And when clicking to reverse tokens, it should - keep the full range selected""", goldenFileName: "deposit_page_5_percent_set_to_full_range_reverse_tokens", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + keep the full range selected""", + goldenFileName: "deposit_page_5_percent_set_to_full_range_reverse_tokens", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("5-percent-range-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("5-percent-range-button"))); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("full-range-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("full-range-button"))); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the 5% range button, it should set 5% up - and 5% down of the current price for the min and max prices""", goldenFileName: "deposit_page_set_5_percent_range", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + zGoldenTest( + """When clicking the 5% range button, it should set 5% up + and 5% down of the current price for the min and max prices""", + goldenFileName: "deposit_page_set_5_percent_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("5-percent-range-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("5-percent-range-button"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the 5% range button, it should set 5% up + zGoldenTest( + """When clicking the 5% range button, it should set 5% up and 5% down of the current price for the min and max prices. And when clicking to reverse tokens, it should keep the 5% range selected but - now with the reverse tokens range ratio""", goldenFileName: "deposit_page_set_5_percent_range_reverse_tokens", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + now with the reverse tokens range ratio""", + goldenFileName: "deposit_page_set_5_percent_range_reverse_tokens", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("5-percent-range-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("5-percent-range-button"))); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the 20% range button, it should set 20% up - and 20% down of the current price for the min and max prices""", goldenFileName: "deposit_page_set_20_percent_range", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + zGoldenTest( + """When clicking the 20% range button, it should set 20% up + and 20% down of the current price for the min and max prices""", + goldenFileName: "deposit_page_set_20_percent_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("20-percent-range-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("20-percent-range-button"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the 20% range button, it should set 20% up + zGoldenTest( + """When clicking the 20% range button, it should set 20% up and 20% down of the current price for the min and max prices. And when clicking to reverse tokens, it should keep the 20% range selected but - now with the reverse tokens range ratio""", goldenFileName: "deposit_page_set_20_percent_range_reverse_tokens", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + now with the reverse tokens range ratio""", + goldenFileName: "deposit_page_set_20_percent_range_reverse_tokens", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("20-percent-range-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("20-percent-range-button"))); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the 50% range button, it should set 50% up - and 50% down of the current price for the min and max prices""", goldenFileName: "deposit_page_set_50_percent_range", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + zGoldenTest( + """When clicking the 50% range button, it should set 50% up + and 50% down of the current price for the min and max prices""", + goldenFileName: "deposit_page_set_50_percent_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("50-percent-range-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("50-percent-range-button"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the 50% range button, it should set 50% up + zGoldenTest( + """When clicking the 50% range button, it should set 50% up and 50% down of the current price for the min and max prices. And when clicking to reverse tokens, it should keep the 50% range selected but - now with the reverse tokens range ratio""", goldenFileName: "deposit_page_set_50_percent_range_reverse_tokens", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); - - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + now with the reverse tokens range ratio""", + goldenFileName: "deposit_page_set_50_percent_range_reverse_tokens", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.tap(find.byKey(const Key("50-percent-range-button"))); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("50-percent-range-button"))); + await tester.pumpAndSettle(); - zGoldenTest("When typing a max price, but then selecting the full range button, it should set it to infinity", - goldenFileName: "deposit_page_max_price_set_to_full_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + zGoldenTest( + "When typing a max price, but then selecting the full range button, it should set it to infinity", + goldenFileName: "deposit_page_max_price_set_to_full_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("full-range-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); + FocusManager.instance.primaryFocus?.unfocus(); + + await tester.tap(find.byKey(const Key("full-range-button"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the percentage range button,but then typing a custom max price, + zGoldenTest( + """When clicking the percentage range button,but then typing a custom max price, and reversing tokens, it should keep the typed max price. And the min price should be the one from the percentage range""", - goldenFileName: "deposit_page_set_percentage_range_then_type_max_price_reverse_tokens", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_set_percentage_range_then_type_max_price_reverse_tokens", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("50-percent-range-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("50-percent-range-button"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When clicking the percentage range button,but then typing a custom min price, + zGoldenTest( + """When clicking the percentage range button,but then typing a custom min price, and reversing tokens, it should keep the typed min price and the max price should be the one from the percentage range""", - goldenFileName: "deposit_page_set_percentage_range_then_type_min_price_reverse_tokens", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_set_percentage_range_then_type_min_price_reverse_tokens", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("50-percent-range-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("50-percent-range-button"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "1216"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "1216"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When typing a min and max price and then clicking the full range button, + zGoldenTest( + """When typing a min and max price and then clicking the full range button, it should set the min price to 0 and the max price to infinity""", - goldenFileName: "deposit_page_min_and_max_price_set_to_full_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_min_and_max_price_set_to_full_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "2"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "2"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.tap(find.byKey(const Key("full-range-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("full-range-button"))); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( - "When there's a invalid range, the deposit section should be disabled (with opacity) and cannot be clicked or typed", - goldenFileName: "deposit_page_invalid_range_deposit_section", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + "When there's a invalid range, the deposit section should be disabled (with opacity) and cannot be clicked or typed", + goldenFileName: "deposit_page_invalid_range_deposit_section", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "2"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "2"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); - FocusManager.instance.primaryFocus?.unfocus(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); + FocusManager.instance.primaryFocus?.unfocus(); - await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); - await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("deposit-button"))); - await tester.pumpAndSettle(); - }); - }); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("deposit-button"))); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( "When inputing the base token amount, the quote amount token should be automatically calculated", goldenFileName: "deposit_page_input_base_token_amount", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1187,7 +1330,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); @@ -1201,7 +1344,7 @@ void main() { goldenFileName: "deposit_page_input_quote_token_amount", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1212,7 +1355,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -1228,7 +1371,7 @@ void main() { goldenFileName: "deposit_page_input_base_token_amount_and_reverse", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1239,7 +1382,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); @@ -1258,7 +1401,7 @@ void main() { goldenFileName: "deposit_page_input_quote_token_amount_and_reverse", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1269,7 +1412,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -1286,7 +1429,7 @@ void main() { goldenFileName: "deposit_page_input_base_token_amount_reversed", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1297,7 +1440,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); @@ -1314,7 +1457,7 @@ void main() { goldenFileName: "deposit_page_input_quote_token_amount_reversed", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1325,7 +1468,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); @@ -1337,72 +1480,78 @@ void main() { }, ); - zGoldenTest("""When inputing the base token amount with the tokens reversed, + zGoldenTest( + """When inputing the base token amount with the tokens reversed, then turning them normal, the quote token amount should now be the previous base token amount, and the new base token amount should be automatically calculated""", - goldenFileName: "deposit_page_input_base_token_amount_and_reverse_back", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_input_base_token_amount_and_reverse_back", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When inputing the quote token amount with the tokens reversed, + zGoldenTest( + """When inputing the quote token amount with the tokens reversed, then turning them normal, the base token amount should now be the previous quote token amount, and the new quote token amount should be automatically calculated""", - goldenFileName: "deposit_page_input_quote_token_amount_and_reverse_back", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_input_quote_token_amount_and_reverse_back", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); - await tester.pumpAndSettle(); - }); - }); + await tester.tap(find.byKey(const Key("reverse-tokens-not-reversed"))); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( "When inputing the base token amount, then changing the range, the quote token amount should be recalculated", goldenFileName: "deposit_page_input_base_token_amount_and_change_range", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1413,7 +1562,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); @@ -1439,7 +1588,7 @@ void main() { goldenFileName: "deposit_page_input_quote_token_amount_and_change_range", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1450,7 +1599,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -1472,87 +1621,91 @@ void main() { ); zGoldenTest( - "When inputing the base token amount, reversing the tokens and then changing the range, the base token amount should be recalculated", - goldenFileName: "deposit_page_input_base_token_amount_reverse_tokens_and_change_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + "When inputing the base token amount, reversing the tokens and then changing the range, the base token amount should be recalculated", + goldenFileName: "deposit_page_input_base_token_amount_reverse_tokens_and_change_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "90000"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "90000"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( - "When inputing the quote token amount, reversing the tokens and then changing the range, the quote token amount should be recalculated", - goldenFileName: "deposit_page_input_quote_token_amount_reverse_tokens_and_change_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + "When inputing the quote token amount, reversing the tokens and then changing the range, the quote token amount should be recalculated", + goldenFileName: "deposit_page_input_quote_token_amount_reverse_tokens_and_change_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "90000"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "90000"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( "When inputing a range, then inputing the base token amount, the quote token amount should be automatically calculated", goldenFileName: "deposit_page_input_range_then_input_base_token_amount", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1563,7 +1716,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "0.000001"); @@ -1589,7 +1742,7 @@ void main() { goldenFileName: "deposit_page_input_range_then_input_quote_token_amount", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1600,7 +1753,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "0.000001"); @@ -1625,7 +1778,7 @@ void main() { goldenFileName: "deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1636,7 +1789,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); @@ -1668,7 +1821,7 @@ void main() { goldenFileName: "deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); @@ -1679,7 +1832,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); @@ -1705,142 +1858,152 @@ void main() { }, ); - zGoldenTest("""When inputing base token amount, and then setting a max price out of range, + zGoldenTest( + """When inputing base token amount, and then setting a max price out of range, it should keep the quote token amount and disable the base token input""", - goldenFileName: "deposit_page_input_base_token_amount_then_set_max_price_out_of_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_input_base_token_amount_then_set_max_price_out_of_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "0.00000001"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0.00000001"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When inputing quote token amount, and then setting a min price out of range, + zGoldenTest( + """When inputing quote token amount, and then setting a min price out of range, it should keep the base token amount and disable the quote token input""", - goldenFileName: "deposit_page_input_quote_token_amount_then_set_min_price_out_of_range", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_input_quote_token_amount_then_set_min_price_out_of_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "2"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "2"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When inputing base token amount, reversing the tokens, and then setting a max price out of range, + zGoldenTest( + """When inputing base token amount, reversing the tokens, and then setting a max price out of range, it should keep the quote token amount and disable the base token input""", - goldenFileName: "deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("max-price-selector")), "3"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "3"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); + }, + ); - zGoldenTest("""When inputing quote token amount, reversing the tokens, and then setting a min price out of range, + zGoldenTest( + """When inputing quote token amount, reversing the tokens, and then setting a min price out of range, it should keep the base token amount and disable the quote token input""", - goldenFileName: "deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range", - (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); + goldenFileName: "deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("min-price-selector")), "70000"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("min-price-selector")), "70000"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - }); - }); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); + }, + ); zGoldenTest( "When the user is is not connected, it should show the connect wallet button instead of the deposit button", goldenFileName: "deposit_page_not_connected", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => wallet.signer).thenReturn(null); @@ -1853,7 +2016,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); }); }, @@ -1867,7 +2030,7 @@ void main() { goldenFileName: "deposit_page_not_connected_deposit_button_click", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); when(() => wallet.signer).thenReturn(null); @@ -1879,7 +2042,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("deposit-button"))); @@ -1894,15 +2057,15 @@ void main() { goldenFileName: "deposit_page_no_amount_deposit_button", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final signer = SignerMock(); when(() => wallet.signer).thenReturn(signer); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(any(), network: any(named: "network"))).thenAnswer( - (_) => Future.value(0.0), - ); + when( + () => cubit.getWalletTokenAmount(any(), network: any(named: "network")), + ).thenAnswer((_) => Future.value(0.0)); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); @@ -1924,15 +2087,15 @@ void main() { goldenFileName: "deposit_page_not_enough_base_token_balance_deposit_button", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final signer = SignerMock(); when(() => wallet.signer).thenReturn(signer); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(any(), network: any(named: "network"))).thenAnswer( - (_) => Future.value(0.0), - ); + when( + () => cubit.getWalletTokenAmount(any(), network: any(named: "network")), + ).thenAnswer((_) => Future.value(0.0)); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); @@ -1957,21 +2120,25 @@ void main() { goldenFileName: "deposit_page_not_enough_quote_token_balance_deposit_button", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final signer = SignerMock(); when(() => wallet.signer).thenReturn(signer); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(32567352673), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(0), - ); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(32567352673)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(0)); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); @@ -1995,21 +2162,25 @@ void main() { goldenFileName: "deposit_page_not_enough_base_token_balance_deposit_button_after_connecting", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final signerStreamController = StreamController.broadcast(); final signer = SignerMock(); when(() => wallet.signer).thenReturn(null); when(() => wallet.signerStream).thenAnswer((_) => signerStreamController.stream); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(0), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(0), - ); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(0)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(0)); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); @@ -2036,21 +2207,157 @@ void main() { goldenFileName: "deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final signerStreamController = StreamController.broadcast(); final signer = SignerMock(); when(() => wallet.signer).thenReturn(null); when(() => wallet.signerStream).thenAnswer((_) => signerStreamController.stream); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(347537253), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(0), - ); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(347537253)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(0)); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); + + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.pumpAndSettle(); + + signerStreamController.add(signer); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When the user is connected, type an amount to deposit, and have enough balance of both tokens + the deposit button should be enabled""", + goldenFileName: "deposit_page_enough_balance_deposit_button", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); + + final signer = SignerMock(); + + when(() => wallet.signer).thenReturn(signer); + when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(347537253)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(32576352673)); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); + + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When the min range is out of range, and the user does not have quote token balance + but has enough balance of base token, the deposit button should be enabled""", + goldenFileName: "deposit_page_min_range_out_of_range_deposit_button", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); + + final signer = SignerMock(); + + when(() => wallet.signer).thenReturn(signer); + when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(347537253)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(0)); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); + + await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); + await tester.pumpAndSettle(); + + await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When the max range is out of range, and the user does not have base token balance + but has enough balance of quote token, the deposit button should be enabled""", + goldenFileName: "deposit_page_max_range_out_of_range_deposit_button", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; + final currentPriceAsTick = BigInt.from(174072); + + final signer = SignerMock(); + + when(() => wallet.signer).thenReturn(signer); + when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(0)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(3237526)); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); @@ -2059,184 +2366,77 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.enterText(find.byKey(const Key("min-price-selector")), "0.0000001"); await tester.pumpAndSettle(); - signerStreamController.add(signer); + await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); + await tester.pumpAndSettle(); + + await tester.enterText(find.byKey(const Key("max-price-selector")), "0.000001"); + await tester.pumpAndSettle(); + + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); await tester.pumpAndSettle(); }); }, ); zGoldenTest( - """When the user is connected, type an amount to deposit, and have enough balance of both tokens - the deposit button should be enabled""", - goldenFileName: "deposit_page_enough_balance_deposit_button", + "When clicking the enabled deposit button, it should show the preview modal of the deposit", + goldenFileName: "deposit_page_preview_modal", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final signer = SignerMock(); when(() => wallet.signer).thenReturn(signer); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(347537253), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(32576352673), - ); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(347537253)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(32576352673)); + when(() => cubit.selectYield(any())).thenAnswer((_) => Future.value()); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("deposit-button"))); + await tester.pumpAndSettle(); }); }, ); - zGoldenTest("""When the min range is out of range, and the user does not have quote token balance - but has enough balance of base token, the deposit button should be enabled""", - goldenFileName: "deposit_page_min_range_out_of_range_deposit_button", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); - - final signer = SignerMock(); - - when(() => wallet.signer).thenReturn(signer); - when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(347537253), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(0), - ); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); - await tester.pumpAndSettle(); - - await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); - await tester.pumpAndSettle(); - - await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("""When the max range is out of range, and the user does not have base token balance - but has enough balance of quote token, the deposit button should be enabled""", - goldenFileName: "deposit_page_max_range_out_of_range_deposit_button", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); - - final signer = SignerMock(); - - when(() => wallet.signer).thenReturn(signer); - when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(0), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(3237526), - ); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); - await tester.pumpAndSettle(); - - await tester.enterText(find.byKey(const Key("min-price-selector")), "0.0000001"); - await tester.pumpAndSettle(); - - await tester.enterText(find.byKey(const Key("max-price-selector")), "0"); - await tester.pumpAndSettle(); - - await tester.enterText(find.byKey(const Key("max-price-selector")), "0.000001"); - await tester.pumpAndSettle(); - - await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("When clicking the enabled deposit button, it should show the preview modal of the deposit", - goldenFileName: "deposit_page_preview_modal", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; - final currentPriceAsTick = BigInt.from(174072); - - final signer = SignerMock(); - - when(() => wallet.signer).thenReturn(signer); - when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(347537253), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(32576352673), - ); - when(() => cubit.selectYield(any(), any())).thenAnswer((_) => Future.value()); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); - when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("yield-card-24h"))); - await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); - await tester.pumpAndSettle(); - - await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("deposit-button"))); - await tester.pumpAndSettle(); - }); - }); - zGoldenTest( """When the base token amount input is not empty, and the pool tick is null, the quote token input should be loading""", goldenFileName: "deposit_page_quote_token_input_loading", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); @@ -2246,7 +2446,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); @@ -2261,7 +2461,7 @@ void main() { goldenFileName: "deposit_page_base_token_input_loading", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); @@ -2271,7 +2471,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -2287,7 +2487,7 @@ void main() { goldenFileName: "deposit_page_quote_token_input_enabled_after_loading", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); @@ -2297,7 +2497,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); @@ -2313,7 +2513,7 @@ void main() { goldenFileName: "deposit_page_base_token_input_enabled_after_loading", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); @@ -2323,7 +2523,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -700)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -2349,7 +2549,9 @@ void main() { await tester.enterText(find.byKey(const Key("slippage-text-field")), expectedSlippageCallback.value.toString()); await tester.enterText( - find.byKey(const Key("deadline-textfield")), expectedDeadlineCallback.inMinutes.toString()); + find.byKey(const Key("deadline-textfield")), + expectedDeadlineCallback.inMinutes.toString(), + ); FocusManager.instance.primaryFocus?.unfocus(); await tester.pumpAndSettle(); @@ -2446,7 +2648,9 @@ void main() { await tester.enterText(find.byKey(const Key("slippage-text-field")), expectedSlippageCallback.value.toString()); await tester.enterText( - find.byKey(const Key("deadline-textfield")), expectedDeadlineCallback.inMinutes.toString()); + find.byKey(const Key("deadline-textfield")), + expectedDeadlineCallback.inMinutes.toString(), + ); FocusManager.instance.primaryFocus?.unfocus(); await tester.pumpAndSettle(); @@ -2459,7 +2663,7 @@ void main() { the ones from the cubit""", goldenFileName: "deposit_page_deposit_settings_dropdown", (tester) async { - final expectedDepositSettings = DepositSettingsDto(maxSlippage: 32.1, deadlineMinutes: 98); + const expectedDepositSettings = DepositSettingsDto(maxSlippage: 32.1, deadlineMinutes: 98); when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); when(() => cubit.depositSettings).thenReturn(expectedDepositSettings); @@ -2483,9 +2687,13 @@ void main() { await tester.pumpAndSettle(); await tester.enterText( - find.byKey(const Key("slippage-text-field")), "0.7"); // expected slippage to be shown on reopening + find.byKey(const Key("slippage-text-field")), + "0.7", + ); // expected slippage to be shown on reopening await tester.enterText( - find.byKey(const Key("deadline-textfield")), "76"); // expected deadline to be shown on reopening + find.byKey(const Key("deadline-textfield")), + "76", + ); // expected deadline to be shown on reopening FocusManager.instance.primaryFocus?.unfocus(); await tester.pumpAndSettle(); @@ -2508,33 +2716,36 @@ void main() { const expectedDeadline = Duration(minutes: 76); await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final signer = SignerMock(); when(() => wallet.signer).thenReturn(signer); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(347537253), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(32576352673), - ); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(347537253)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(32576352673)); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); - when(() => cubit.selectYield(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.selectedYieldTimeframe).thenReturn(YieldTimeFrame.day); + when(() => cubit.selectYield(any())).thenAnswer((_) async => () {}); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("yield-card-24h"))); + await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("deposit-settings-button"))); @@ -2595,7 +2806,7 @@ void main() { goldenFileName: "deposit_page_pool_tick_update_deposit_amount", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().best24hYield; + final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.from(174072); final nextPriceAsTick = BigInt.from(261892); final poolTickStreamController = StreamController.broadcast(); @@ -2604,15 +2815,19 @@ void main() { when(() => wallet.signer).thenReturn(signer); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); - when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(347537253), - ); - when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, - network: any(named: "network"))).thenAnswer( - (_) => Future.value(32576352673), - ); - when(() => cubit.selectYield(any(), any())).thenAnswer((_) => Future.value()); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(347537253)); + when( + () => cubit.getWalletTokenAmount( + selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"), + ), + ).thenAnswer((_) => Future.value(32576352673)); + when(() => cubit.selectYield(any())).thenAnswer((_) => Future.value()); when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); when(() => cubit.selectedYield).thenReturn(selectedYield); when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); @@ -2620,7 +2835,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("yield-card-24h"))); + await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2634,4 +2849,331 @@ void main() { }); }, ); + + zGoldenTest( + """When scrolling yields in the 24h timeframe, then clicking to switch to the + 7d timeframe, it should reset the scroll position""", + goldenFileName: "deposit_page_reset_scroll_position_on_timeframe_change", + (tester) async { + await tester.runAsync(() async { + final yields = YieldsDto.fixture().copyWith( + pools: List.generate(10, (index) => YieldDto.fixture()) + .mapIndexed( + (index, element) => element.copyWith( + yield24h: index, + yield7d: index * 2, + yield30d: index * 3, + protocol: ProtocolDto.fixture().copyWith(name: "$index"), + ), + ) + .toList(), + ); + + when(() => cubit.state).thenReturn(DepositState.success(yields)); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When hovering the 24h timeframe button in the success state, + it should have an scale and opacity state""", + goldenFileName: "deposit_page_24h_timeframe_hover", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.hover(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When hovering the 7d timeframe button in the success state, + it should have an scale and opacity state""", + goldenFileName: "deposit_page_7d_timeframe_hover", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.hover(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When hovering the 30d timeframe button in the success state, + it should have an scale and opacity state""", + goldenFileName: "deposit_page_30d_timeframe_hover", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.hover(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When hovering the 90d timeframe button in the success state, + it should have an scale and opacity state""", + goldenFileName: "deposit_page_90d_timeframe_hover", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.hover(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + "When clicking the 7d timeframe in the success state, it should change the yields timeframe", + goldenFileName: "deposit_page_7d_timeframe", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + "When clicking the 30d timeframe in the success state, it should change the yields timeframe", + goldenFileName: "deposit_page_30d_timeframe", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + "When clicking the 90d timeframe in the success state, it should change the yields timeframe", + goldenFileName: "deposit_page_90d_timeframe", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + """When clicking the 90d timeframe in the success state and then coming back to the 24h timeframe, + it should change the yields timeframe correctly back to 24h""", + goldenFileName: "deposit_page_90d_timeframe_back_to_24h", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + group( + """When the state is sucess, + all pools should be displayed, with + two per page, sorted descending by + the selected timeframe""", + () { + final poolsList = [ + YieldDto.fixture().copyWith(yield24h: 1, yield7d: 10, yield30d: 100, yield90d: 1000), + YieldDto.fixture().copyWith(yield24h: 2, yield7d: 20, yield30d: 200, yield90d: 2000), + YieldDto.fixture().copyWith(yield24h: 3, yield7d: 30, yield30d: 300, yield90d: 3000), + YieldDto.fixture().copyWith(yield24h: 4, yield7d: 40, yield30d: 400, yield90d: 4000), + YieldDto.fixture().copyWith(yield24h: 5, yield7d: 50, yield30d: 500, yield90d: 5000), + ]; + + zGoldenTest("24h timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_24h_timeframe", (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }); + + zGoldenTest("24h timeframe Page 2", goldenFileName: "deposit_page_pools_page_2_24h_timeframe", (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }); + + zGoldenTest("7d timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_7d_timeframe", (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }); + + zGoldenTest("""7d timeframe Page 2""", goldenFileName: "deposit_page_pools_page_2_7d_timeframe", (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + }); + }); + + zGoldenTest("30d timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_30d_timeframe", (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }); + + zGoldenTest("""30d timeframe Page 2""", goldenFileName: "deposit_page_pools_page_2_30d_timeframe", ( + tester, + ) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + }); + }); + + zGoldenTest("90d timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_90d_timeframe", (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }); + }); + + zGoldenTest("""90d timeframe Page 2""", goldenFileName: "deposit_page_pools_page_2_90d_timeframe", ( + tester, + ) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + }); + }); + }, + ); + + zGoldenTest( + """When the state is sucess, + and the pools amount are odd, + the last page should show only + the last pool""", + goldenFileName: "deposit_page_odd_pools_last_page", + (tester) async { + final poolsList = [ + YieldDto.fixture().copyWith(yield24h: 1), + YieldDto.fixture().copyWith(yield24h: 2), + YieldDto.fixture().copyWith(yield24h: 3), + YieldDto.fixture().copyWith(yield24h: 4), + YieldDto.fixture().copyWith(yield24h: 5), + ]; + + await tester.runAsync(() async { + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("next-yield-page-button"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + "When hovering the page indicator for the pools pages, it should scale", + goldenFileName: "deposit_page_pools_page_indicator_hover", + (tester) async { + await tester.runAsync(() async { + when( + () => cubit.state, + ).thenReturn(DepositState.success(YieldsDto(pools: List.generate(10, (index) => YieldDto.fixture())))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.hover(find.byKey(const Key("yield-page-indicator-3"))); + await tester.pumpAndSettle(); + }); + }, + ); + + zGoldenTest( + "When clicking the page indicator for the pools pages, it should navigate to the page", + goldenFileName: "deposit_page_pools_page_indicator_click", + (tester) async { + await tester.runAsync(() async { + when(() => cubit.state).thenReturn( + DepositState.success( + YieldsDto(pools: List.generate(10, (index) => YieldDto.fixture().copyWith(yield24h: index + 1))), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.tap(find.byKey(const Key("yield-page-indicator-3"))); + await tester.pumpAndSettle(); + }); + }, + ); } diff --git a/test/app/create/deposit/goldens/deposit_page_24h_timeframe_hover.png b/test/app/create/deposit/goldens/deposit_page_24h_timeframe_hover.png new file mode 100644 index 0000000..d3533ab Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_24h_timeframe_hover.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_30d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_30d_timeframe.png new file mode 100644 index 0000000..ec80025 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_30d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_30d_timeframe_hover.png b/test/app/create/deposit/goldens/deposit_page_30d_timeframe_hover.png new file mode 100644 index 0000000..c6285fa Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_30d_timeframe_hover.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png index 6241290..6c94c78 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png index 82dbc33..b5fea83 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_7d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_7d_timeframe.png new file mode 100644 index 0000000..063c248 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_7d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_7d_timeframe_hover.png b/test/app/create/deposit/goldens/deposit_page_7d_timeframe_hover.png new file mode 100644 index 0000000..36c735d Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_7d_timeframe_hover.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_90d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_90d_timeframe.png new file mode 100644 index 0000000..887035b Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_90d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_90d_timeframe_back_to_24h.png b/test/app/create/deposit/goldens/deposit_page_90d_timeframe_back_to_24h.png new file mode 100644 index 0000000..93b05b0 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_90d_timeframe_back_to_24h.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_90d_timeframe_hover.png b/test/app/create/deposit/goldens/deposit_page_90d_timeframe_hover.png new file mode 100644 index 0000000..9bfde2f Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_90d_timeframe_hover.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png b/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png index b9d38eb..fab4258 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png and b/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png b/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png index 6e693b5..c656d76 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png and b/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_calculate_price.png b/test/app/create/deposit/goldens/deposit_page_calculate_price.png index 6241290..6c94c78 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_calculate_price.png and b/test/app/create/deposit/goldens/deposit_page_calculate_price.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png b/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png index 82dbc33..b5fea83 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png and b/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png index b75a826..e6887f4 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png index 3ccf344..9034e0e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png index 309e2d0..957565f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png index b4afc0f..9bea3b0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png index dc9e34d..090174d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png index 2828102..a297ba2 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png index 2f2d337..76d9234 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png index 39695b3..5da54f6 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png index f7c5856..ae674cb 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png index 0e0361b..1e303fb 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png index 3f8898d..ef5ed54 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png index 5328a13..8b218c2 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png index 3d2511a..f885fb8 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png index b5184a2..1b66c06 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png index 37e357e..66e45e4 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png index ff31ff4..6b61a02 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png index b9a9a7b..50aabf1 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png index 93c1b24..ef1a0cc 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png index 5af7b96..4b45567 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png index 3383d7a..1b8701d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png index a5251f7..22d2a38 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png index 7e93329..36773e0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png index 0197fa0..8d539ff 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png index cc44599..8384557 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png index 2deeea8..d81f5bc 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png index 3383d7a..1b8701d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png index 5328a13..8b218c2 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png b/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png index ece93f4..ac16386 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png and b/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png b/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png index 2f3612a..ba0c0ae 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png and b/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png index 33080a1..be6c190 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png index 6241290..6c94c78 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png index 93bfdee..9943f27 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png and b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png index 00fcd6e..a963ad2 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png index 6241290..6c94c78 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png index 6fb3f2b..d57fd8c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png index ce2b92a..eded629 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png index eac4708..aed6935 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png index 6241290..6c94c78 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png index 0d835e1..828ecd0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png index 074c735..1437194 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_connected.png b/test/app/create/deposit/goldens/deposit_page_not_connected.png index 6c622f5..b79055d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_connected.png and b/test/app/create/deposit/goldens/deposit_page_not_connected.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png b/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png index a17e4a9..21ba558 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png and b/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png index a2ba963..51b1ad8 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png index af62a99..0446b7b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png index d0e52e2..265cd80 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png index 1e30bd0..442717d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_odd_pools_last_page.png b/test/app/create/deposit/goldens/deposit_page_odd_pools_last_page.png new file mode 100644 index 0000000..a9603bd Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_odd_pools_last_page.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png b/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png index 681c5f1..029e6a9 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png and b/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_1.png b/test/app/create/deposit/goldens/deposit_page_pools_page_1.png new file mode 100644 index 0000000..0c518a2 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_1.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_1_24h_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_1_24h_timeframe.png new file mode 100644 index 0000000..0c518a2 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_1_24h_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_1_30d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_1_30d_timeframe.png new file mode 100644 index 0000000..dcf9a83 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_1_30d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_1_7d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_1_7d_timeframe.png new file mode 100644 index 0000000..0b43e5c Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_1_7d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_1_90d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_1_90d_timeframe.png new file mode 100644 index 0000000..0f49f2d Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_1_90d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_2.png b/test/app/create/deposit/goldens/deposit_page_pools_page_2.png new file mode 100644 index 0000000..8ed3b57 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_2.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_2_24h_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_2_24h_timeframe.png new file mode 100644 index 0000000..8ed3b57 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_2_24h_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_2_30d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_2_30d_timeframe.png new file mode 100644 index 0000000..076c35e Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_2_30d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_2_7d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_2_7d_timeframe.png new file mode 100644 index 0000000..1e1a646 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_2_7d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_2_90d_timeframe.png b/test/app/create/deposit/goldens/deposit_page_pools_page_2_90d_timeframe.png new file mode 100644 index 0000000..651cacd Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_2_90d_timeframe.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_indicator_click.png b/test/app/create/deposit/goldens/deposit_page_pools_page_indicator_click.png new file mode 100644 index 0000000..a8f36b9 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_indicator_click.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pools_page_indicator_hover.png b/test/app/create/deposit/goldens/deposit_page_pools_page_indicator_hover.png new file mode 100644 index 0000000..907c3d8 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pools_page_indicator_hover.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_preview_modal.png b/test/app/create/deposit/goldens/deposit_page_preview_modal.png index c5dc992..bbd1059 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_preview_modal.png and b/test/app/create/deposit/goldens/deposit_page_preview_modal.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png b/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png index 19e7205..10ddff7 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png and b/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png b/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png index a9a8899..ec94e1d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png and b/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png b/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png index 8a04412..d44dbbb 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png and b/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_reset_scroll_position_on_timeframe_change.png b/test/app/create/deposit/goldens/deposit_page_reset_scroll_position_on_timeframe_change.png new file mode 100644 index 0000000..e1c4e03 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_reset_scroll_position_on_timeframe_change.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png index 582b944..618b18e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png b/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png index 6b81ddd..16daf4a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png and b/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png b/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png index 228603c..570277a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png and b/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png b/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png index 6b81ddd..16daf4a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png and b/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png index 6fb62de..df7580c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png index cf766ae..4a3d9de 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png index 304e7a0..2bbbfeb 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png index ca9ff06..89c42ec 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png index 658d8b5..ecadfea 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png index 600413b..08773bc 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png index 20cbbfb..dd0f5bc 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png index 6706285..ef9013f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success.png b/test/app/create/deposit/goldens/deposit_page_success.png index 1d7bfc1..93b05b0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success.png and b/test/app/create/deposit/goldens/deposit_page_success.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png b/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png index 0e2e1ea..df60690 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png and b/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity_local_filter_set.png b/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity_local_filter_set.png index 3de9b1a..72c666d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity_local_filter_set.png and b/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity_local_filter_set.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success_mobile.png b/test/app/create/deposit/goldens/deposit_page_success_mobile.png index a6db298..1360cec 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success_mobile.png and b/test/app/create/deposit/goldens/deposit_page_success_mobile.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success_not_filtered_by_min_liquidity.png b/test/app/create/deposit/goldens/deposit_page_success_not_filtered_by_min_liquidity.png index da241c5..abce625 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success_not_filtered_by_min_liquidity.png and b/test/app/create/deposit/goldens/deposit_page_success_not_filtered_by_min_liquidity.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png b/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png index e3dd6a7..76375c5 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png and b/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png differ diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png b/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png index 5c0b7c5..d9caebd 100644 Binary files a/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png and b/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png differ diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart index 3315a32..ad0f7e3 100644 --- a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart +++ b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart @@ -17,6 +17,7 @@ import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/core/pool_service.dart'; import 'package:zup_app/core/slippage.dart'; @@ -80,20 +81,14 @@ void main() { when(() => cubit.setup()).thenAnswer((_) async {}); when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(V3V4PoolConstants.maxTick)); when(() => cubit.latestPoolTick).thenReturn(BigInt.from(3247)); - when(() => cubit.stream).thenAnswer((_) => Stream.value( - PreviewDepositModalState.initial( - token0Allowance: BigInt.zero, - token1Allowance: BigInt.zero, - ), - )); - - when(() => cubit.state).thenReturn( - PreviewDepositModalState.initial( - token0Allowance: BigInt.zero, - token1Allowance: BigInt.zero, - ), + when(() => cubit.stream).thenAnswer( + (_) => Stream.value(PreviewDepositModalState.initial(token0Allowance: BigInt.zero, token1Allowance: BigInt.zero)), ); + when( + () => cubit.state, + ).thenReturn(PreviewDepositModalState.initial(token0Allowance: BigInt.zero, token1Allowance: BigInt.zero)); + when( () => cubit.deposit( deadline: any(named: "deadline"), @@ -126,44 +121,44 @@ void main() { TextEditingController? token1DepositAmountController, Duration deadline = const Duration(minutes: 30), Slippage slippage = Slippage.halfPercent, - }) => - goldenDeviceBuilder( - Builder(builder: (context) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - await ZupModal.show(context, - size: const Size(450, 650), - padding: const EdgeInsets.only(left: 20), - title: "Preview Deposit", - content: BlocProvider.value( - value: cubit, - child: PreviewDepositModal( - yieldTimeFrame: YieldTimeFrame.day, - deadline: deadline, - maxSlippage: slippage, - currentYield: customYield ?? currentYield, - isReversed: isReversed, - minPrice: minPrice, - maxPrice: maxPrice, - token0DepositAmountController: token0DepositAmountController ?? TextEditingController(text: "1"), - token1DepositAmountController: token1DepositAmountController ?? TextEditingController(text: "3"), - ), - )); - }); - - return const SizedBox(); - }), - ); - - zGoldenTest( - "When initializing the widget, it should call setup in the cubit", - (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); + }) => goldenDeviceBuilder( + Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + await ZupModal.show( + context, + size: const Size(450, 650), + padding: const EdgeInsets.only(left: 20), + title: "Preview Deposit", + content: BlocProvider.value( + value: cubit, + child: PreviewDepositModal( + yieldTimeFrame: YieldTimeFrame.day, + deadline: deadline, + maxSlippage: slippage, + currentYield: customYield ?? currentYield, + isReversed: isReversed, + minPrice: minPrice, + maxPrice: maxPrice, + token0DepositAmountController: token0DepositAmountController ?? TextEditingController(text: "1"), + token1DepositAmountController: token1DepositAmountController ?? TextEditingController(text: "3"), + ), + ), + ); + }); - verify(() => cubit.setup()).called(1); - }, + return const SizedBox(); + }, + ), ); + zGoldenTest("When initializing the widget, it should call setup in the cubit", (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + + verify(() => cubit.setup()).called(1); + }); + zGoldenTest( "When the state is waiting transaction, it should show a snackbar", goldenFileName: "preview_deposit_modal_waiting_transaction", @@ -189,10 +184,12 @@ void main() { goldenFileName: "preview_deposit_modal_waiting_transaction_button", (tester) async { when(() => cubit.state).thenReturn( - const PreviewDepositModalState.waitingTransaction(txId: "txID", type: WaitingTransactionType.deposit)); + const PreviewDepositModalState.waitingTransaction(txId: "txID", type: WaitingTransactionType.deposit), + ); when(() => cubit.stream).thenAnswer( (_) => Stream.value( - const PreviewDepositModalState.waitingTransaction(txId: "txID", type: WaitingTransactionType.deposit)), + const PreviewDepositModalState.waitingTransaction(txId: "txID", type: WaitingTransactionType.deposit), + ), ); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); @@ -211,10 +208,12 @@ void main() { const yieldNetwork = AppNetworks.sepolia; when(() => cubit.state).thenReturn( - const PreviewDepositModalState.waitingTransaction(txId: "txID", type: WaitingTransactionType.deposit)); + const PreviewDepositModalState.waitingTransaction(txId: "txID", type: WaitingTransactionType.deposit), + ); when(() => cubit.stream).thenAnswer( (_) => Stream.value( - const PreviewDepositModalState.waitingTransaction(txId: txId, type: WaitingTransactionType.deposit)), + const PreviewDepositModalState.waitingTransaction(txId: txId, type: WaitingTransactionType.deposit), + ), ); await tester.pumpDeviceBuilder( @@ -256,10 +255,7 @@ void main() { when(() => cubit.stream).thenAnswer((_) { return Stream.fromFutures([ Future.value( - const PreviewDepositModalState.waitingTransaction( - txId: '', - type: WaitingTransactionType.approve, - ), + const PreviewDepositModalState.waitingTransaction(txId: '', type: WaitingTransactionType.approve), ), // Assuming that the waiting transaction state will display a snackbar Future.value(const PreviewDepositModalState.approveSuccess(txId: '', symbol: "TOKE SYM")), @@ -290,11 +286,9 @@ void main() { goldenFileName: "preview_deposit_modal_in_range", (tester) async { await tester.pumpDeviceBuilder( - await goldenBuilder( - minPrice: (price: 0, isInfinity: true), - maxPrice: (price: 0, isInfinity: true), - ), - wrapper: GoldenConfig.localizationsWrapper()); + await goldenBuilder(minPrice: (price: 0, isInfinity: true), maxPrice: (price: 0, isInfinity: true)), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); }, @@ -316,11 +310,9 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder( - await goldenBuilder( - minPrice: (price: minPrice, isInfinity: false), - maxPrice: (price: 0, isInfinity: true), - ), - wrapper: GoldenConfig.localizationsWrapper()); + await goldenBuilder(minPrice: (price: minPrice, isInfinity: false), maxPrice: (price: 0, isInfinity: true)), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); }, @@ -342,11 +334,9 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder( - await goldenBuilder( - minPrice: (price: 0, isInfinity: true), - maxPrice: (price: maxPrice, isInfinity: false), - ), - wrapper: GoldenConfig.localizationsWrapper()); + await goldenBuilder(minPrice: (price: 0, isInfinity: true), maxPrice: (price: maxPrice, isInfinity: false)), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); }, @@ -371,11 +361,12 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder( - await goldenBuilder( - minPrice: (price: minPrice, isInfinity: false), - maxPrice: (price: maxPrice, isInfinity: false), - ), - wrapper: GoldenConfig.localizationsWrapper()); + await goldenBuilder( + minPrice: (price: minPrice, isInfinity: false), + maxPrice: (price: maxPrice, isInfinity: false), + ), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); }, @@ -385,8 +376,10 @@ void main() { "When initializing with isReversed true, it should start with the tokens reversed", goldenFileName: "preview_deposit_modal_reversed", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder(isReversed: true), - wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpDeviceBuilder( + await goldenBuilder(isReversed: true), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); }, @@ -482,24 +475,21 @@ void main() { final token0Allowance = BigInt.from(100); const depositAmount = 200.43; - when(() => cubit.state).thenReturn( - PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: BigInt.zero, - ), - ); + when( + () => cubit.state, + ).thenReturn(PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: BigInt.zero)); when(() => cubit.stream).thenAnswer((_) { - return Stream.value(PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: BigInt.zero, - )); + return Stream.value( + PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: BigInt.zero), + ); }); await tester.pumpDeviceBuilder( await goldenBuilder( - token0DepositAmountController: TextEditingController(text: depositAmount.toString()), - token1DepositAmountController: TextEditingController(text: "0")), + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: "0"), + ), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -517,24 +507,21 @@ void main() { when(() => cubit.approveToken(any(), any())).thenAnswer((_) => Future.value()); - when(() => cubit.state).thenReturn( - PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: BigInt.zero, - ), - ); + when( + () => cubit.state, + ).thenReturn(PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: BigInt.zero)); when(() => cubit.stream).thenAnswer((_) { - return Stream.value(PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: BigInt.zero, - )); + return Stream.value( + PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: BigInt.zero), + ); }); await tester.pumpDeviceBuilder( await goldenBuilder( - token0DepositAmountController: TextEditingController(text: depositAmount.toString()), - token1DepositAmountController: TextEditingController(text: "0")), + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: "0"), + ), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -568,16 +555,19 @@ void main() { ); when(() => cubit.stream).thenAnswer((_) { - return Stream.value(PreviewDepositModalState.initial( - token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), - token1Allowance: token1Allowance, - )); + return Stream.value( + PreviewDepositModalState.initial( + token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), + token1Allowance: token1Allowance, + ), + ); }); await tester.pumpDeviceBuilder( await goldenBuilder( - token0DepositAmountController: TextEditingController(text: depositAmount.toString()), - token1DepositAmountController: TextEditingController(text: depositAmount.toString())), + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: depositAmount.toString()), + ), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -605,16 +595,19 @@ void main() { ); when(() => cubit.stream).thenAnswer((_) { - return Stream.value(PreviewDepositModalState.initial( - token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), - token1Allowance: token1Allowance, - )); + return Stream.value( + PreviewDepositModalState.initial( + token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), + token1Allowance: token1Allowance, + ), + ); }); await tester.pumpDeviceBuilder( await goldenBuilder( - token0DepositAmountController: TextEditingController(text: depositAmount.toString()), - token1DepositAmountController: TextEditingController(text: depositAmount.toString())), + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: depositAmount.toString()), + ), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -644,23 +637,20 @@ void main() { const deposit1Amount = 110.2; when(() => cubit.state).thenReturn( - PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: token1Allowance, - ), + PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: token1Allowance), ); when(() => cubit.stream).thenAnswer((_) { - return Stream.value(PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: token1Allowance, - )); + return Stream.value( + PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: token1Allowance), + ); }); await tester.pumpDeviceBuilder( await goldenBuilder( - token0DepositAmountController: TextEditingController(text: deposit0Amount.toString()), - token1DepositAmountController: TextEditingController(text: deposit1Amount.toString())), + token0DepositAmountController: TextEditingController(text: deposit0Amount.toString()), + token1DepositAmountController: TextEditingController(text: deposit1Amount.toString()), + ), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -687,17 +677,13 @@ void main() { const isMinPriceInfinity = false; when(() => cubit.state).thenReturn( - PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: token1Allowance, - ), + PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: token1Allowance), ); when(() => cubit.stream).thenAnswer((_) { - return Stream.value(PreviewDepositModalState.initial( - token0Allowance: token0Allowance, - token1Allowance: token1Allowance, - )); + return Stream.value( + PreviewDepositModalState.initial(token0Allowance: token0Allowance, token1Allowance: token1Allowance), + ); }); await tester.pumpDeviceBuilder( @@ -858,16 +844,7 @@ void main() { goldenFileName: "preview_deposit_modal_range_prices_reversed_manually", (tester) async { await tester.pumpDeviceBuilder( - await goldenBuilder( - minPrice: ( - isInfinity: false, - price: 0.04, - ), - maxPrice: ( - isInfinity: false, - price: 1.2, - ), - ), + await goldenBuilder(minPrice: (isInfinity: false, price: 0.04), maxPrice: (isInfinity: false, price: 1.2)), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -907,14 +884,8 @@ void main() { await tester.pumpDeviceBuilder( await goldenBuilder( isReversed: true, - minPrice: ( - isInfinity: false, - price: 1200, - ), - maxPrice: ( - isInfinity: false, - price: 3000, - ), + minPrice: (isInfinity: false, price: 1200), + maxPrice: (isInfinity: false, price: 3000), ), wrapper: GoldenConfig.localizationsWrapper(), ); @@ -931,23 +902,25 @@ void main() { await tester.runAsync(() async { await tester.pumpDeviceBuilder( await goldenDeviceBuilder( - Builder(builder: (context) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - PreviewDepositModal( - yieldTimeFrame: YieldTimeFrame.day, - currentYield: currentYield, - isReversed: true, - minPrice: (isInfinity: true, price: 0), - maxPrice: (isInfinity: true, price: 0), - token0DepositAmountController: TextEditingController(text: "1200"), - token1DepositAmountController: TextEditingController(text: "4300"), - deadline: const Duration(minutes: 30), - maxSlippage: Slippage.halfPercent, - ).show(context, currentPoolTick: BigInt.from(121475)); - }); - - return const SizedBox(); - }), + Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + PreviewDepositModal( + yieldTimeFrame: YieldTimeFrame.day, + currentYield: currentYield, + isReversed: true, + minPrice: (isInfinity: true, price: 0), + maxPrice: (isInfinity: true, price: 0), + token0DepositAmountController: TextEditingController(text: "1200"), + token1DepositAmountController: TextEditingController(text: "4300"), + deadline: const Duration(minutes: 30), + maxSlippage: Slippage.halfPercent, + ).show(context, currentPoolTick: BigInt.from(121475)); + }); + + return const SizedBox(); + }, + ), device: GoldenDevice.mobile, ), wrapper: GoldenConfig.localizationsWrapper(), @@ -964,23 +937,25 @@ void main() { await tester.runAsync(() async { await tester.pumpDeviceBuilder( await goldenDeviceBuilder( - Builder(builder: (context) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - PreviewDepositModal( - yieldTimeFrame: YieldTimeFrame.day, - currentYield: currentYield, - isReversed: true, - minPrice: (isInfinity: true, price: 0), - maxPrice: (isInfinity: true, price: 0), - token0DepositAmountController: TextEditingController(text: "1200"), - token1DepositAmountController: TextEditingController(text: "4300"), - deadline: const Duration(minutes: 30), - maxSlippage: Slippage.halfPercent, - ).show(context, currentPoolTick: BigInt.from(121475)); - }); - - return const SizedBox(); - }), + Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + PreviewDepositModal( + yieldTimeFrame: YieldTimeFrame.day, + currentYield: currentYield, + isReversed: true, + minPrice: (isInfinity: true, price: 0), + maxPrice: (isInfinity: true, price: 0), + token0DepositAmountController: TextEditingController(text: "1200"), + token1DepositAmountController: TextEditingController(text: "4300"), + deadline: const Duration(minutes: 30), + maxSlippage: Slippage.halfPercent, + ).show(context, currentPoolTick: BigInt.from(121475)); + }); + + return const SizedBox(); + }, + ), device: GoldenDevice.pc, ), wrapper: GoldenConfig.localizationsWrapper(), @@ -1030,36 +1005,42 @@ void main() { }, ); - zGoldenTest("When the token amounts controllers change its amount, it should reflect in the UI", - goldenFileName: "preview_deposit_modal_token_amounts_change", (tester) async { - final token0DepositAmountController = TextEditingController(text: "0"); - final token1DepositAmountController = TextEditingController(text: "0"); + zGoldenTest( + "When the token amounts controllers change its amount, it should reflect in the UI", + goldenFileName: "preview_deposit_modal_token_amounts_change", + (tester) async { + final token0DepositAmountController = TextEditingController(text: "0"); + final token1DepositAmountController = TextEditingController(text: "0"); - when(() => cubit.state).thenReturn( - PreviewDepositModalState.initial( - token0Allowance: EthereumConstants.uint256Max, - token1Allowance: EthereumConstants.uint256Max, - ), - ); + when(() => cubit.state).thenReturn( + PreviewDepositModalState.initial( + token0Allowance: EthereumConstants.uint256Max, + token1Allowance: EthereumConstants.uint256Max, + ), + ); - when(() => cubit.stream).thenAnswer((_) { - return Stream.value(PreviewDepositModalState.initial( - token0Allowance: EthereumConstants.uint256Max, - token1Allowance: EthereumConstants.uint256Max, - )); - }); + when(() => cubit.stream).thenAnswer((_) { + return Stream.value( + PreviewDepositModalState.initial( + token0Allowance: EthereumConstants.uint256Max, + token1Allowance: EthereumConstants.uint256Max, + ), + ); + }); - await tester.pumpDeviceBuilder( + await tester.pumpDeviceBuilder( await goldenBuilder( token0DepositAmountController: token0DepositAmountController, token1DepositAmountController: token1DepositAmountController, ), - wrapper: GoldenConfig.localizationsWrapper(scaffoldMessengerKey: scaffoldMessengerKey)); + wrapper: GoldenConfig.localizationsWrapper(scaffoldMessengerKey: scaffoldMessengerKey), + ); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); - token0DepositAmountController.text = "1200"; - token1DepositAmountController.text = "4300"; - await tester.pumpAndSettle(); - }); + token0DepositAmountController.text = "1200"; + token1DepositAmountController.text = "4300"; + await tester.pumpAndSettle(); + }, + ); } diff --git a/test/app/create/goldens/create_page_initial_stage.png b/test/app/create/goldens/create_page_initial_stage.png index 3a276bb..f77a6e2 100644 Binary files a/test/app/create/goldens/create_page_initial_stage.png and b/test/app/create/goldens/create_page_initial_stage.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png b/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png index 10918e7..15d3fed 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png and b/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png b/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png index 6af423e..0af79fd 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png and b/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png b/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png index 3a276bb..f77a6e2 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png and b/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_mobile.png b/test/app/create/goldens/create_page_select_tokens_stage_mobile.png index 502018b..b411768 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_mobile.png and b/test/app/create/goldens/create_page_select_tokens_stage_mobile.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png index bd3a0a6..4745c21 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png index 282e363..029ca05 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png index bd3a0a6..4745c21 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png index 9bd5edf..5d2032e 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png index 282e363..029ca05 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png b/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png index bd3a0a6..4745c21 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png and b/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png b/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png index 10918e7..15d3fed 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png and b/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png b/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png index d03fc5e..2ee692a 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png and b/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png differ diff --git a/test/core/cache_test.dart b/test/core/cache_test.dart index db7635d..e6e7bc4 100644 --- a/test/core/cache_test.dart +++ b/test/core/cache_test.dart @@ -20,64 +20,62 @@ void main() { sut = Cache(sharedPreferencesWithCache); }); - test("When calling `saveHidingClosedPositionsStatus` it should use the correct key and store the correct status", - () async { - when(() => sharedPreferencesWithCache.setBool(any(), any())).thenAnswer((_) async => true); - - const status = true; - await sut.saveHidingClosedPositionsStatus(status: status); - - verify(() => sharedPreferencesWithCache.setBool(CacheKey.hidingClosedPositions.key, status)).called(1); - }); - - test("When calling `getHidingClosedPositionsStatus` it should use the correct key and return the correct status", - () async { - const status = true; + test( + "When calling `saveHidingClosedPositionsStatus` it should use the correct key and store the correct status", + () async { + when(() => sharedPreferencesWithCache.setBool(any(), any())).thenAnswer((_) async => true); - when(() => sharedPreferencesWithCache.getBool(any())).thenReturn(status); + const status = true; + await sut.saveHidingClosedPositionsStatus(status: status); - final result = await sut.getHidingClosedPositionsStatus(); + verify(() => sharedPreferencesWithCache.setBool(CacheKey.hidingClosedPositions.key, status)).called(1); + }, + ); - expect(result, status); + test( + "When calling `getHidingClosedPositionsStatus` it should use the correct key and return the correct status", + () async { + const status = true; - verify(() => sharedPreferencesWithCache.getBool(CacheKey.hidingClosedPositions.key)).called(1); - }); + when(() => sharedPreferencesWithCache.getBool(any())).thenReturn(status); - test("When calling `getHidingClosedPositionsStatus` and the shared preferences return null, it should return false", - () async { - when(() => sharedPreferencesWithCache.getBool(any())).thenReturn(null); + final result = await sut.getHidingClosedPositionsStatus(); - final result = await sut.getHidingClosedPositionsStatus(); + expect(result, status); - expect(result, false); - }); + verify(() => sharedPreferencesWithCache.getBool(CacheKey.hidingClosedPositions.key)).called(1); + }, + ); test( - "When calling `saveDepositSettings` it should use the correct key to store the deposit settings", + "When calling `getHidingClosedPositionsStatus` and the shared preferences return null, it should return false", () async { - final settings = DepositSettingsDto(); + when(() => sharedPreferencesWithCache.getBool(any())).thenReturn(null); - when(() => sharedPreferencesWithCache.setString(any(), any())).thenAnswer((_) async => true); - await sut.saveDepositSettings(settings); + final result = await sut.getHidingClosedPositionsStatus(); - verify(() => sharedPreferencesWithCache.setString(CacheKey.depositSettings.key, any())).called(1); + expect(result, false); }, ); + test("When calling `saveDepositSettings` it should use the correct key to store the deposit settings", () async { + const settings = DepositSettingsDto(); + + when(() => sharedPreferencesWithCache.setString(any(), any())).thenAnswer((_) async => true); + await sut.saveDepositSettings(settings); + + verify(() => sharedPreferencesWithCache.setString(CacheKey.depositSettings.key, any())).called(1); + }); + test( "When calling `saveDepositSettings` it should convert the dto to json, and store the json as a string", () async { - final settings = DepositSettingsDto(); + const settings = DepositSettingsDto(); when(() => sharedPreferencesWithCache.setString(any(), any())).thenAnswer((_) async => true); await sut.saveDepositSettings(settings); - verify( - () => sharedPreferencesWithCache.setString( - any(), - jsonEncode(settings.toJson()).toString(), - ), - ).called(1); + verify(() => sharedPreferencesWithCache.setString(any(), jsonEncode(settings.toJson()).toString())).called(1); }, ); @@ -90,7 +88,7 @@ void main() { }); test("When calling `getDepositSettings` it should convert the returned saved json to a deposit settings dto", () { - final settings = DepositSettingsDto(deadlineMinutes: 321, maxSlippage: 7384); + const settings = DepositSettingsDto(deadlineMinutes: 321, maxSlippage: 7384); when(() => sharedPreferencesWithCache.getString(any())).thenReturn(jsonEncode(settings.toJson())); @@ -125,15 +123,12 @@ void main() { test("when calling 'savePoolSearchSettings' it should save under the correct key", () { when(() => sharedPreferencesWithCache.setString(any(), any())).thenAnswer((_) async => true); - final settings = PoolSearchSettingsDto( - minLiquidityUSD: 12786, - ); + final settings = PoolSearchSettingsDto(minLiquidityUSD: 12786); sut.savePoolSearchSettings(settings: settings); - verify(() => sharedPreferencesWithCache.setString( - CacheKey.poolSearchSettings.key, - jsonEncode(settings.toJson()), - )).called(1); + verify( + () => sharedPreferencesWithCache.setString(CacheKey.poolSearchSettings.key, jsonEncode(settings.toJson())), + ).called(1); }); test("when calling 'getPoolSearchSettings' it should get under the correct key", () { diff --git a/test/core/dtos/token_group_dto_test.dart b/test/core/dtos/token_group_dto_test.dart new file mode 100644 index 0000000..9f67db2 --- /dev/null +++ b/test/core/dtos/token_group_dto_test.dart @@ -0,0 +1,14 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; +import 'package:zup_app/core/enums/app_environment.dart'; + +void main() { + test( + 'When calling `logoUrl` in a token group, it should build the url from the zup api based on the environment', + () { + final tokenGroup = TokenGroupDto.fixture(); + + expect(tokenGroup.logoUrl, '${AppEnvironment.current.apiUrl}/static/group-icons/${tokenGroup.id}.png'); + }, + ); +} diff --git a/test/core/dtos/yield_dto_test.dart b/test/core/dtos/yield_dto_test.dart index b33cfc1..473f5de 100644 --- a/test/core/dtos/yield_dto_test.dart +++ b/test/core/dtos/yield_dto_test.dart @@ -3,6 +3,7 @@ import 'package:web3kit/core/ethereum_constants.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; void main() { test("When calling `isToken0Native` and the token0 address in the yield network is zero, it should return true", () { @@ -10,8 +11,9 @@ void main() { expect( YieldDto.fixture() .copyWith( - chainId: network.chainId, - token0: TokenDto.fixture().copyWith(addresses: {network.chainId: EthereumConstants.zeroAddress})) + chainId: network.chainId, + token0: TokenDto.fixture().copyWith(addresses: {network.chainId: EthereumConstants.zeroAddress}), + ) .isToken0Native, true, ); @@ -22,8 +24,9 @@ void main() { expect( YieldDto.fixture() .copyWith( - chainId: network.chainId, - token1: TokenDto.fixture().copyWith(addresses: {network.chainId: EthereumConstants.zeroAddress})) + chainId: network.chainId, + token1: TokenDto.fixture().copyWith(addresses: {network.chainId: EthereumConstants.zeroAddress}), + ) .isToken1Native, true, ); @@ -33,7 +36,10 @@ void main() { const network = AppNetworks.sepolia; expect( YieldDto.fixture() - .copyWith(chainId: network.chainId, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x1"})) + .copyWith( + chainId: network.chainId, + token0: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x1"}), + ) .isToken0Native, false, ); @@ -43,9 +49,40 @@ void main() { const network = AppNetworks.sepolia; expect( YieldDto.fixture() - .copyWith(chainId: network.chainId, token1: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x1"})) + .copyWith( + chainId: network.chainId, + token1: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x1"}), + ) .isToken1Native, false, ); }); + + test("When calling 'yieldTimeframed' passing a 24h timeframe, it should get the 24h yield", () { + const yield24h = 261782; + final currentYield = YieldDto.fixture().copyWith(yield24h: yield24h); + + expect(currentYield.yieldTimeframed(YieldTimeFrame.day), yield24h); + }); + + test("When calling 'yieldTimeframed' passing a 7d timeframe, it should get the 90d yield", () { + const yield7d = 819028190; + final currentYield = YieldDto.fixture().copyWith(yield7d: yield7d); + + expect(currentYield.yieldTimeframed(YieldTimeFrame.week), yield7d); + }); + + test("When calling 'yieldTimeframed' passing a 30d timeframe, it should get the 90d yield", () { + const yield30d = 8.9787678; + final currentYield = YieldDto.fixture().copyWith(yield30d: yield30d); + + expect(currentYield.yieldTimeframed(YieldTimeFrame.month), yield30d); + }); + + test("When calling 'yieldTimeframed' passing a 90d timeframe, it should get the 90d yield", () { + const yield90d = 12718728.222; + final currentYield = YieldDto.fixture().copyWith(yield90d: yield90d); + + expect(currentYield.yieldTimeframed(YieldTimeFrame.threeMonth), yield90d); + }); } diff --git a/test/core/dtos/yields_dto_test.dart b/test/core/dtos/yields_dto_test.dart new file mode 100644 index 0000000..152faf9 --- /dev/null +++ b/test/core/dtos/yields_dto_test.dart @@ -0,0 +1,114 @@ +import 'package:collection/collection.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; +import 'package:zup_app/core/dtos/yields_dto.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; + +void main() { + test("When calling 'poolsSortedBy24hYield' it should short pools descending by 24h yield", () { + final pools = [ + YieldDto.fixture().copyWith(yield24h: 100), + YieldDto.fixture().copyWith(yield24h: 87), + YieldDto.fixture().copyWith(yield24h: 0), + YieldDto.fixture().copyWith(yield24h: 2000), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedBy24hYield, pools.sorted((a, b) => b.yield24h.compareTo(a.yield24h))); + }); + + test("When calling 'poolsSortedBy7dYield' it should short pools descending by 7d yield", () { + final pools = [ + YieldDto.fixture().copyWith(yield7d: 12681), + YieldDto.fixture().copyWith(yield7d: 111), + YieldDto.fixture().copyWith(yield7d: 0), + YieldDto.fixture().copyWith(yield7d: 21971972891), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedBy7dYield, pools.sorted((a, b) => b.yield7d.compareTo(a.yield7d))); + }); + + test("When calling 'poolsSortedBy30dYield' it should short pools descending by 30d yield", () { + final pools = [ + YieldDto.fixture().copyWith(yield30d: 089889888), + YieldDto.fixture().copyWith(yield30d: 2222), + YieldDto.fixture().copyWith(yield30d: 0), + YieldDto.fixture().copyWith(yield30d: 12615654), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedBy30dYield, pools.sorted((a, b) => b.yield30d.compareTo(a.yield30d))); + }); + + test("When calling 'poolsSortedBy90dYield' it should short pools descending by 90d yield", () { + final pools = [ + YieldDto.fixture().copyWith(yield90d: 978888), + YieldDto.fixture().copyWith(yield90d: 121), + YieldDto.fixture().copyWith(yield90d: 0), + YieldDto.fixture().copyWith(yield90d: 329087902), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedBy90dYield, pools.sorted((a, b) => b.yield90d.compareTo(a.yield90d))); + }); + + test("when calling 'poolsSortedByTimeframe' passing YieldTimeFrame.day it should return poolsSortedBy24hYield", () { + final pools = [ + YieldDto.fixture().copyWith(yield24h: 100), + YieldDto.fixture().copyWith(yield24h: 87), + YieldDto.fixture().copyWith(yield24h: 0), + YieldDto.fixture().copyWith(yield24h: 2000), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedByTimeframe(YieldTimeFrame.day), yields.poolsSortedBy24hYield); + }); + + test("when calling 'poolsSortedByTimeframe' passing YieldTimeFrame.week it should return poolsSortedBy7dYield", () { + final pools = [ + YieldDto.fixture().copyWith(yield7d: 12681), + YieldDto.fixture().copyWith(yield7d: 111), + YieldDto.fixture().copyWith(yield7d: 0), + YieldDto.fixture().copyWith(yield7d: 21971972891), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedByTimeframe(YieldTimeFrame.week), yields.poolsSortedBy7dYield); + }); + + test("when calling 'poolsSortedByTimeframe' passing YieldTimeFrame.month it should return poolsSortedBy30dYield", () { + final pools = [ + YieldDto.fixture().copyWith(yield30d: 089889888), + YieldDto.fixture().copyWith(yield30d: 2222), + YieldDto.fixture().copyWith(yield30d: 0), + YieldDto.fixture().copyWith(yield30d: 12615654), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedByTimeframe(YieldTimeFrame.month), yields.poolsSortedBy30dYield); + }); + + test( + "when calling 'poolsSortedByTimeframe' passing YieldTimeFrame.threeMonth it should return poolsSortedBy90dYield", + () { + final pools = [ + YieldDto.fixture().copyWith(yield90d: 978888), + YieldDto.fixture().copyWith(yield90d: 121), + YieldDto.fixture().copyWith(yield90d: 0), + YieldDto.fixture().copyWith(yield90d: 329087902), + ]; + + final yields = YieldsDto.fixture().copyWith(pools: pools); + + expect(yields.poolsSortedByTimeframe(YieldTimeFrame.threeMonth), yields.poolsSortedBy90dYield); + }, + ); +} diff --git a/test/core/enums/yield_timeframe_test.dart b/test/core/enums/yield_timeframe_test.dart new file mode 100644 index 0000000..00a9b3c --- /dev/null +++ b/test/core/enums/yield_timeframe_test.dart @@ -0,0 +1,36 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; + +void main() { + test("When calling 'isDay' and the timeframe is day, it should return true", () { + expect(YieldTimeFrame.day.isDay, true); + }); + + test("When calling 'isDay' and the timeframe is not day, it should return false", () { + expect(YieldTimeFrame.week.isDay, false); + }); + + test("When calling isWeek and the timeframe is week, it should return true", () { + expect(YieldTimeFrame.week.isWeek, true); + }); + + test("When calling isWeek and the timeframe is not week, it should return false", () { + expect(YieldTimeFrame.day.isWeek, false); + }); + + test("When calling isMonth and the timeframe is month, it should return true", () { + expect(YieldTimeFrame.month.isMonth, true); + }); + + test("When calling isMonth and the timeframe is not month, it should return false", () { + expect(YieldTimeFrame.day.isMonth, false); + }); + + test("When calling isThreeMonth and the timeframe is threeMonth, it should return true", () { + expect(YieldTimeFrame.threeMonth.isThreeMonth, true); + }); + + test("When calling isThreeMonth and the timeframe is not threeMonth, it should return false", () { + expect(YieldTimeFrame.day.isThreeMonth, false); + }); +} diff --git a/test/core/enums/zup_navigator_paths_test.dart b/test/core/enums/zup_navigator_paths_test.dart index c3831a7..9fe5a37 100644 --- a/test/core/enums/zup_navigator_paths_test.dart +++ b/test/core/enums/zup_navigator_paths_test.dart @@ -1,5 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:zup_app/core/enums/zup_navigator_paths.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; import 'package:zup_app/zup_app.dart'; void main() { @@ -15,10 +16,15 @@ void main() { ); }); - test("The route params name for deposit page, should be `token0`, `token1` and network", () { - expect( - ZupNavigatorPaths.deposit.routeParamsName, - (param0: "token0", param1: "token1", param3: "network"), - ); + test("The route params names type for deposit should be correct", () { + expect(ZupNavigatorPaths.deposit.routeParamsNames().runtimeType, ZupDepositRouteParamsNames); + }); + + test("The route params names type for new position should be correct", () { + expect(ZupNavigatorPaths.newPosition.routeParamsNames().runtimeType, ZupNewPositionRouteParamsNames); + }); + + test("The route params names type for initial should be correct", () { + expect(ZupNavigatorPaths.initial.routeParamsNames().runtimeType, ZupInitialRouteParamsNames); }); } diff --git a/test/core/repositories/tokens_repository_test.dart b/test/core/repositories/tokens_repository_test.dart index a6c8070..64ea073 100644 --- a/test/core/repositories/tokens_repository_test.dart +++ b/test/core/repositories/tokens_repository_test.dart @@ -2,6 +2,7 @@ import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_list_dto.dart'; import 'package:zup_app/core/dtos/token_price_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; @@ -21,65 +22,63 @@ void main() { const query = "dale"; const network = AppNetworks.sepolia; - when(() => dio.get(any(), queryParameters: any(named: "queryParameters"), cancelToken: any(named: "cancelToken"))) - .thenAnswer( - (_) async => Response( - data: [TokenDto.fixture().toJson()], - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.get( + any(), + queryParameters: any(named: "queryParameters"), + cancelToken: any(named: "cancelToken"), ), + ).thenAnswer( + (_) async => Response(data: [TokenDto.fixture().toJson()], statusCode: 200, requestOptions: RequestOptions()), ); await sut.searchToken(query, network); - verify(() => dio.get( - "/tokens/search", - cancelToken: any(named: "cancelToken"), - queryParameters: { - "chainId": network.chainId, - "query": query, - }, - )); + verify( + () => dio.get( + "/tokens/search", + cancelToken: any(named: "cancelToken"), + queryParameters: {"chainId": network.chainId, "query": query}, + ), + ); }); test("When calling 'searchToken' and the network is all networks, it should not pass the chainId param", () async { const query = "dale"; - when(() => dio.get(any(), queryParameters: any(named: "queryParameters"), cancelToken: any(named: "cancelToken"))) - .thenAnswer( - (_) async => Response( - data: [TokenDto.fixture().toJson()], - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.get( + any(), + queryParameters: any(named: "queryParameters"), + cancelToken: any(named: "cancelToken"), ), + ).thenAnswer( + (_) async => Response(data: [TokenDto.fixture().toJson()], statusCode: 200, requestOptions: RequestOptions()), ); await sut.searchToken(query, AppNetworks.allNetworks); - verify(() => dio.get( - "/tokens/search", - cancelToken: any(named: "cancelToken"), - queryParameters: { - "query": query, - }, - )); + verify( + () => dio.get( + "/tokens/search", + cancelToken: any(named: "cancelToken"), + queryParameters: {"query": query}, + ), + ); }); test("When calling `searchToken` it should correctly parse the response", () async { - final tokens = [ - TokenDto.fixture(), - TokenDto.fixture(), - TokenDto.fixture(), - ]; - - when(() => dio.get(any(), queryParameters: any(named: "queryParameters"), cancelToken: any(named: "cancelToken"))) - .thenAnswer( + final tokens = [TokenDto.fixture(), TokenDto.fixture(), TokenDto.fixture()]; + + when( + () => dio.get( + any(), + queryParameters: any(named: "queryParameters"), + cancelToken: any(named: "cancelToken"), + ), + ).thenAnswer( (_) async => Response( - data: [ - TokenDto.fixture().toJson(), - TokenDto.fixture().toJson(), - TokenDto.fixture().toJson(), - ], + data: [TokenDto.fixture().toJson(), TokenDto.fixture().toJson(), TokenDto.fixture().toJson()], statusCode: 200, requestOptions: RequestOptions(), ), @@ -90,62 +89,45 @@ void main() { expect(response, tokens); }); - test("When calling `getPopularTokens` and the passed network is all networks, it should not pass the chainId param", - () async { - when(() => dio.get(any(), queryParameters: any(named: "queryParameters"))).thenAnswer( - (_) async => Response( - data: [TokenDto.fixture().toJson()], - statusCode: 200, - requestOptions: RequestOptions(), - ), - ); + test( + "When calling `getTokenList` and the passed network is all networks, it should not pass the chainId param", + () async { + when(() => dio.get(any(), queryParameters: any(named: "queryParameters"))).thenAnswer( + (_) async => Response(data: TokenListDto.fixture().toJson(), statusCode: 200, requestOptions: RequestOptions()), + ); - await sut.getPopularTokens(AppNetworks.allNetworks); + await sut.getTokenList(AppNetworks.allNetworks); - verify(() => dio.get("/tokens/popular", queryParameters: {})).called(1); - }); + verify(() => dio.get("/tokens/list", queryParameters: {})).called(1); + }, + ); test( - "When calling `getPopularTokens` and the passed network is not all networks, it should not the correct chainId param", - () async { - const network = AppNetworks.scroll; + "When calling `getTokenList` and the passed network is not all networks, it should not the correct chainId param", + () async { + const network = AppNetworks.scroll; - when(() => dio.get(any(), queryParameters: any(named: "queryParameters"))).thenAnswer( - (_) async => Response( - data: [TokenDto.fixture().toJson()], - statusCode: 200, - requestOptions: RequestOptions(), - ), - ); + when(() => dio.get(any(), queryParameters: any(named: "queryParameters"))).thenAnswer( + (_) async => Response(data: TokenListDto.fixture().toJson(), statusCode: 200, requestOptions: RequestOptions()), + ); - await sut.getPopularTokens(AppNetworks.scroll); + await sut.getTokenList(AppNetworks.scroll); - verify(() => dio.get("/tokens/popular", queryParameters: { - "chainId": network.chainId, - })).called(1); - }); + verify(() => dio.get("/tokens/list", queryParameters: {"chainId": network.chainId})).called(1); + }, + ); test("When calling `getTokenPrice` it should call the correct endpoint with correct params", () async { const address = "0x123"; const network = AppNetworks.sepolia; when(() => dio.get(any(), queryParameters: any(named: "queryParameters"))).thenAnswer( - (_) async => Response( - data: TokenPriceDto.fixture().toJson(), - statusCode: 200, - requestOptions: RequestOptions(), - ), + (_) async => Response(data: TokenPriceDto.fixture().toJson(), statusCode: 200, requestOptions: RequestOptions()), ); await sut.getTokenPrice(address, network); - verify(() => dio.get( - "/tokens/price", - queryParameters: { - "address": address, - "chainId": network.chainId, - }, - )).called(1); + verify(() => dio.get("/tokens/price", queryParameters: {"address": address, "chainId": network.chainId})).called(1); }); test("When calling `getTokenPrice` it should correctly parse the response", () async { @@ -154,11 +136,7 @@ void main() { final tokenPriceDto = TokenPriceDto.fixture(); when(() => dio.get(any(), queryParameters: any(named: "queryParameters"))).thenAnswer( - (_) async => Response( - data: tokenPriceDto.toJson(), - statusCode: 200, - requestOptions: RequestOptions(), - ), + (_) async => Response(data: tokenPriceDto.toJson(), statusCode: 200, requestOptions: RequestOptions()), ); final response = await sut.getTokenPrice(address, network); diff --git a/test/core/repositories/yield_repository_test.dart b/test/core/repositories/yield_repository_test.dart index 78355f8..c2e5d57 100644 --- a/test/core/repositories/yield_repository_test.dart +++ b/test/core/repositories/yield_repository_test.dart @@ -20,55 +20,67 @@ void main() { test("When calling `getSingleNetworkYield` it should call the correct endpoint with the correct params", () async { final yields = YieldsDto.fixture(); - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: {"bestYields": yields.toJson()}, - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), ), + ).thenAnswer( + (_) async => Response(data: {"bestYields": yields.toJson()}, statusCode: 200, requestOptions: RequestOptions()), ); const token0Address = "0x123"; const token1Address = "0x456"; + const group0Id = "0x789"; + const group1Id = "0xabc"; const network = AppNetworks.sepolia; const minTvlUsd = 1213; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd); final blockedProtocolsIds = ["1", "2", "3"]; await sut.getSingleNetworkYield( + group0Id: group0Id, + group1Id: group1Id, blockedProtocolIds: blockedProtocolsIds, token0Address: token0Address, token1Address: token1Address, + network: network, searchSettings: searchSettings, ); verify( - () => dio.post("/pools/search/${network.chainId}", queryParameters: { - "token0Address": token0Address, - "token1Address": token1Address - }, data: { - "filters": { - "minTvlUsd": searchSettings.minLiquidityUSD, - "blockedProtocols": blockedProtocolsIds, - "allowedPoolTypes": [ - "V3", - "V4", - ], - } - }), + () => dio.post( + "/pools/search/${network.chainId}", + queryParameters: { + "token0Address": token0Address, + "token1Address": token1Address, + "group0Id": group0Id, + "group1Id": group1Id, + }, + data: { + "filters": { + "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, + "allowedPoolTypes": ["V3", "V4"], + }, + }, + ), ).called(1); }); test("When the V3 Pool is disabled in the search settings, it should not be included in the request", () async { final yields = YieldsDto.fixture(); - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: {"bestYields": yields.toJson()}, - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), ), + ).thenAnswer( + (_) async => Response(data: {"bestYields": yields.toJson()}, statusCode: 200, requestOptions: RequestOptions()), ); const token0Address = "0x123"; @@ -78,38 +90,42 @@ void main() { final blockedProtocolsIds = ["1", "2", "3"]; await sut.getSingleNetworkYield( + group0Id: null, + group1Id: null, blockedProtocolIds: blockedProtocolsIds, token0Address: token0Address, token1Address: token1Address, + network: network, searchSettings: searchSettings, ); verify( - () => dio.post("/pools/search/${network.chainId}", queryParameters: { - "token0Address": token0Address, - "token1Address": token1Address - }, data: { - "filters": { - "minTvlUsd": searchSettings.minLiquidityUSD, - "blockedProtocols": blockedProtocolsIds, - "allowedPoolTypes": [ - "V4", - ], - } - }), + () => dio.post( + "/pools/search/${network.chainId}", + queryParameters: {"token0Address": token0Address, "token1Address": token1Address}, + data: { + "filters": { + "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, + "allowedPoolTypes": ["V4"], + }, + }, + ), ).called(1); }); test("When the V4 Pool is disabled in the search settings, it should not be included in the request", () async { final yields = YieldsDto.fixture(); - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: {"bestYields": yields.toJson()}, - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), ), + ).thenAnswer( + (_) async => Response(data: {"bestYields": yields.toJson()}, statusCode: 200, requestOptions: RequestOptions()), ); const token0Address = "0x123"; @@ -119,6 +135,8 @@ void main() { final blockedProtocolIds = ["1", "2", "3"]; await sut.getSingleNetworkYield( + group0Id: null, + group1Id: null, blockedProtocolIds: blockedProtocolIds, token0Address: token0Address, token1Address: token1Address, @@ -127,35 +145,38 @@ void main() { ); verify( - () => dio.post("/pools/search/${network.chainId}", queryParameters: { - "token0Address": token0Address, - "token1Address": token1Address - }, data: { - "filters": { - "blockedProtocols": blockedProtocolIds, - "minTvlUsd": searchSettings.minLiquidityUSD, - "allowedPoolTypes": ["V3"], - } - }), + () => dio.post( + "/pools/search/${network.chainId}", + queryParameters: {"token0Address": token0Address, "token1Address": token1Address}, + data: { + "filters": { + "blockedProtocols": blockedProtocolIds, + "minTvlUsd": searchSettings.minLiquidityUSD, + "allowedPoolTypes": ["V3"], + }, + }, + ), ).called(1); }); test("When calling `getYields` it should correctly parse the response", () async { final yields = YieldsDto.fixture(); - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: yields.toJson(), - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), ), - ); + ).thenAnswer((_) async => Response(data: yields.toJson(), statusCode: 200, requestOptions: RequestOptions())); const token0Address = "0x123"; const token1Address = "0x456"; const network = AppNetworks.sepolia; final response = await sut.getSingleNetworkYield( + group0Id: null, + group1Id: null, blockedProtocolIds: [], token0Address: token0Address, token1Address: token1Address, @@ -169,21 +190,27 @@ void main() { test("when calling 'getAllNetworksYield' it should call the correct endpoint with the correct params", () async { final yields = YieldsDto.fixture(); - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: {"bestYields": yields.toJson()}, - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), ), + ).thenAnswer( + (_) async => Response(data: {"bestYields": yields.toJson()}, statusCode: 200, requestOptions: RequestOptions()), ); const token0Id = "0x123"; const token1Id = "0x456"; + const group0Id = "0x789"; + const group1Id = "0xabc"; const minTvlUsd = 1213; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd); final blockedProtocolsIds = ["1", "2", "3"]; await sut.getAllNetworksYield( + group0Id: group0Id, + group1Id: group1Id, blockedProtocolIds: blockedProtocolsIds, token0InternalId: token0Id, token1InternalId: token1Id, @@ -192,126 +219,134 @@ void main() { ); verify( - () => dio.post("/pools/search/all", queryParameters: { - "token0Id": token0Id, - "token1Id": token1Id, - }, data: { - "filters": { - "testnetMode": true, - "minTvlUsd": searchSettings.minLiquidityUSD, - "blockedProtocols": blockedProtocolsIds, - "allowedPoolTypes": [ - "V3", - "V4", - ], - } - }), - ).called(1); - }); - - test( - "when calling 'getAllNetworksYield' and the search settings has the v4 pool disallowed, it should not be included in the params", - () async { - final yields = YieldsDto.fixture(); - - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: {"bestYields": yields.toJson()}, - statusCode: 200, - requestOptions: RequestOptions(), + () => dio.post( + "/pools/search/all", + queryParameters: {"token0Id": token0Id, "token1Id": token1Id, "group0Id": group0Id, "group1Id": group1Id}, + data: { + "filters": { + "testnetMode": true, + "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, + "allowedPoolTypes": ["V3", "V4"], + }, + }, ), - ); - - const token0Id = "0x123"; - const token1Id = "0x456"; - const minTvlUsd = 1213; - final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd, allowV4Search: false); - final blockedProtocolsIds = ["ai", "be", "cd"]; - - await sut.getAllNetworksYield( - blockedProtocolIds: blockedProtocolsIds, - token0InternalId: token0Id, - token1InternalId: token1Id, - searchSettings: searchSettings, - testnetMode: true, - ); - - verify( - () => dio.post("/pools/search/all", queryParameters: { - "token0Id": token0Id, - "token1Id": token1Id, - }, data: { - "filters": { - "testnetMode": true, - "minTvlUsd": searchSettings.minLiquidityUSD, - "blockedProtocols": blockedProtocolsIds, - "allowedPoolTypes": [ - "V3", - ], - } - }), ).called(1); }); test( - "when calling 'getAllNetworksYield' and the search settings has the v3 pool disallowed, it should not be included in the params", - () async { - final yields = YieldsDto.fixture(); - - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: {"bestYields": yields.toJson()}, - statusCode: 200, - requestOptions: RequestOptions(), - ), - ); - - const token0Id = "0x123"; - const token1Id = "0x456"; - const minTvlUsd = 1213; - final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd, allowV3Search: false); - final blockedProtocolsIds = ["ai", "be", "cd"]; - - await sut.getAllNetworksYield( - blockedProtocolIds: blockedProtocolsIds, - token0InternalId: token0Id, - token1InternalId: token1Id, - searchSettings: searchSettings, - testnetMode: true, - ); + "when calling 'getAllNetworksYield' and the search settings has the v4 pool disallowed, it should not be included in the params", + () async { + final yields = YieldsDto.fixture(); + + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), + ), + ).thenAnswer( + (_) async => Response(data: {"bestYields": yields.toJson()}, statusCode: 200, requestOptions: RequestOptions()), + ); + + const token0Id = "0x123"; + const token1Id = "0x456"; + const minTvlUsd = 1213; + final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd, allowV4Search: false); + final blockedProtocolsIds = ["ai", "be", "cd"]; + + await sut.getAllNetworksYield( + group0Id: null, + group1Id: null, + blockedProtocolIds: blockedProtocolsIds, + token0InternalId: token0Id, + token1InternalId: token1Id, + searchSettings: searchSettings, + testnetMode: true, + ); + + verify( + () => dio.post( + "/pools/search/all", + queryParameters: {"token0Id": token0Id, "token1Id": token1Id}, + data: { + "filters": { + "testnetMode": true, + "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, + "allowedPoolTypes": ["V3"], + }, + }, + ), + ).called(1); + }, + ); - verify( - () => dio.post("/pools/search/all", queryParameters: { - "token0Id": token0Id, - "token1Id": token1Id, - }, data: { - "filters": { - "testnetMode": true, - "minTvlUsd": searchSettings.minLiquidityUSD, - "blockedProtocols": blockedProtocolsIds, - "allowedPoolTypes": [ - "V4", - ], - } - }), - ).called(1); - }); + test( + "when calling 'getAllNetworksYield' and the search settings has the v3 pool disallowed, it should not be included in the params", + () async { + final yields = YieldsDto.fixture(); + + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), + ), + ).thenAnswer( + (_) async => Response(data: {"bestYields": yields.toJson()}, statusCode: 200, requestOptions: RequestOptions()), + ); + + const token0Id = "0x123"; + const token1Id = "0x456"; + const minTvlUsd = 1213; + final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd, allowV3Search: false); + final blockedProtocolsIds = ["ai", "be", "cd"]; + + await sut.getAllNetworksYield( + group0Id: null, + group1Id: null, + blockedProtocolIds: blockedProtocolsIds, + token0InternalId: token0Id, + token1InternalId: token1Id, + searchSettings: searchSettings, + testnetMode: true, + ); + + verify( + () => dio.post( + "/pools/search/all", + queryParameters: {"token0Id": token0Id, "token1Id": token1Id}, + data: { + "filters": { + "testnetMode": true, + "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, + "allowedPoolTypes": ["V4"], + }, + }, + ), + ).called(1); + }, + ); test("when calling 'getAllNetworksYield' it should correctly parse the response", () async { final yields = YieldsDto.fixture(); - when(() => dio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).thenAnswer( - (_) async => Response( - data: yields.toJson(), - statusCode: 200, - requestOptions: RequestOptions(), + when( + () => dio.post( + any(), + queryParameters: any(named: "queryParameters"), + data: any(named: "data"), ), - ); + ).thenAnswer((_) async => Response(data: yields.toJson(), statusCode: 200, requestOptions: RequestOptions())); const token0Id = "0x123"; const token1Id = "0x456"; final response = await sut.getAllNetworksYield( + group0Id: null, + group1Id: null, blockedProtocolIds: [], token0InternalId: token0Id, token1InternalId: token1Id, diff --git a/test/core/zup_analytics_test.dart b/test/core/zup_analytics_test.dart index a9de2c4..90ebe9e 100644 --- a/test/core/zup_analytics_test.dart +++ b/test/core/zup_analytics_test.dart @@ -21,8 +21,12 @@ void main() { tokensRepository = TokensRepositoryMock(); sut = ZupAnalytics(firebaseAnalytics, tokensRepository); - when(() => firebaseAnalytics.logEvent(name: any(named: "name"), parameters: any(named: "parameters"))) - .thenAnswer((_) async {}); + when( + () => firebaseAnalytics.logEvent( + name: any(named: "name"), + parameters: any(named: "parameters"), + ), + ).thenAnswer((_) async {}); }); test("when calling `logDeposit` it should log the event with the correct name and params", () async { @@ -34,10 +38,12 @@ void main() { final token0Price = TokenPriceDto.fixture().copyWith(usdPrice: 2100); final token1Price = TokenPriceDto.fixture().copyWith(usdPrice: 21); - when(() => tokensRepository.getTokenPrice(depositedYield.token0.addresses[depositedYield.network.chainId]!, any())) - .thenAnswer((_) async => token0Price); - when(() => tokensRepository.getTokenPrice(depositedYield.token1.addresses[depositedYield.network.chainId]!, any())) - .thenAnswer((_) async => token1Price); + when( + () => tokensRepository.getTokenPrice(depositedYield.token0.addresses[depositedYield.network.chainId]!, any()), + ).thenAnswer((_) async => token0Price); + when( + () => tokensRepository.getTokenPrice(depositedYield.token1.addresses[depositedYield.network.chainId]!, any()), + ).thenAnswer((_) async => token1Price); await sut.logDeposit( depositedYield: depositedYield, @@ -58,7 +64,7 @@ void main() { "wallet_address": "hex:$walletAddress", "pool_address": "hex:${depositedYield.poolAddress}", "protocol_name": depositedYield.protocol.name, - "amount_usd": (amount0 * token0Price.usdPrice) + (amount1 * token1Price.usdPrice) + "amount_usd": (amount0 * token0Price.usdPrice) + (amount1 * token1Price.usdPrice), }, ), ).called(1); @@ -67,20 +73,33 @@ void main() { test("when calling `logSearch` it should log the event with the correct name and params", () async { const token0 = "token0"; const token1 = "token1"; + const group0 = "group0"; + const group1 = "group1"; const network = "network"; - await sut.logSearch(token0: token0, token1: token1, network: network); + await sut.logSearch(token0: token0, token1: token1, network: network, group0: group0, group1: group1); - verify(() => firebaseAnalytics.logEvent(name: "user_searched_yields", parameters: { - "token0_address": "hex:$token0", - "token1_address": "hex:$token1", + verify( + () => firebaseAnalytics.logEvent( + name: "user_searched_yields", + parameters: { + "token0": "id:$token0", + "token1": "id:$token1", + "group0": "id:$group0", + "group1": "id:$group1", "network": network, - })).called(1); + }, + ), + ).called(1); }); test("When calling to log any event and it throws, it should not stop the app", () async { - when(() => firebaseAnalytics.logEvent(name: any(named: "name"), parameters: any(named: "parameters"))) - .thenThrow(Exception()); + when( + () => firebaseAnalytics.logEvent( + name: any(named: "name"), + parameters: any(named: "parameters"), + ), + ).thenThrow(Exception()); await sut.logDeposit( depositedYield: YieldDto.fixture(), diff --git a/test/core/zup_navigator_test.dart b/test/core/zup_navigator_test.dart index 7b40931..b0162c7 100644 --- a/test/core/zup_navigator_test.dart +++ b/test/core/zup_navigator_test.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:routefly/routefly.dart'; import 'package:web3kit/web3kit.dart'; +import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/enums/zup_navigator_paths.dart'; import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; import 'package:zup_app/zup_app.dart'; void main() { @@ -13,15 +15,14 @@ void main() { routerConfig: Routefly.routerConfig( routes: routes, initialPath: ZupNavigatorPaths.initial.path, - routeBuilder: (context, settings, child) => PageRouteBuilder( - settings: settings, - pageBuilder: (context, __, ___) => const SizedBox(), - ), + routeBuilder: (context, settings, child) => + PageRouteBuilder(settings: settings, pageBuilder: (context, __, ___) => const SizedBox()), ), ); - testWidgets("When calling `navigateToNewPosition` it should use routefly to navigate to the new position page", - (tester) async { + testWidgets("When calling `navigateToNewPosition` it should use routefly to navigate to the new position page", ( + tester, + ) async { await tester.pumpWidget(material); await ZupNavigator().navigateToNewPosition(); @@ -29,8 +30,9 @@ void main() { expect(Routefly.currentUri.path, ZupNavigatorPaths.newPosition.path); }); - testWidgets("When calling `navigateToInitial` it should use routefly to navigate to the initial page", - (tester) async { + testWidgets("When calling `navigateToInitial` it should use routefly to navigate to the initial page", ( + tester, + ) async { runApp(material); await ZupNavigator().navigateToInitial(); @@ -46,8 +48,9 @@ void main() { expect(ZupNavigator().currentRoute, ZupNavigatorPaths.newPosition.path); }); - testWidgets("When using `listenable` to listen route change events, it should delegate to Routefly listenable", - (tester) async { + testWidgets("When using `listenable` to listen route change events, it should delegate to Routefly listenable", ( + tester, + ) async { runApp(material); bool listenerCalled = false; @@ -58,4 +61,166 @@ void main() { expect(listenerCalled, true); }); + + testWidgets("When calling `navigateToDeposit` it should pass the params to the query", (tester) async { + runApp(material); + const group0 = "0x123"; + const group1 = "0x456"; + const token0 = "0x789"; + const token1 = "0xabc"; + const network = AppNetworks.mainnet; + + await ZupNavigator().navigateToDeposit( + group0: group0, + group1: group1, + token0: token0, + token1: token1, + network: network, + ); + + expect(Routefly.query.params, { + ZupDepositRouteParamsNames().group0: group0, + ZupDepositRouteParamsNames().group1: group1, + ZupDepositRouteParamsNames().token0: token0, + ZupDepositRouteParamsNames().token1: token1, + ZupDepositRouteParamsNames().network: network.name, + }); + }); + + testWidgets("When calling `navigateToDeposit` without group0, it should not pass it to the query", (tester) async { + runApp(material); + + const group1 = "0x456"; + const token0 = "0x789"; + const token1 = "0xabc"; + const network = AppNetworks.mainnet; + + await ZupNavigator().navigateToDeposit( + group0: null, + group1: group1, + token0: token0, + token1: token1, + network: network, + ); + + expect(Routefly.query.params, { + ZupDepositRouteParamsNames().group1: group1, + ZupDepositRouteParamsNames().token0: token0, + ZupDepositRouteParamsNames().token1: token1, + ZupDepositRouteParamsNames().network: network.name, + }); + }); + + testWidgets("When calling `navigateToDeposit` without group1, it should not pass it to the query", (tester) async { + runApp(material); + + const group0 = "0x456"; + const token0 = "0x789"; + const token1 = "0xabc"; + const network = AppNetworks.mainnet; + + await ZupNavigator().navigateToDeposit( + group0: group0, + group1: null, + token0: token0, + token1: token1, + network: network, + ); + + expect(Routefly.query.params, { + ZupDepositRouteParamsNames().group0: group0, + ZupDepositRouteParamsNames().token0: token0, + ZupDepositRouteParamsNames().token1: token1, + ZupDepositRouteParamsNames().network: network.name, + }); + }); + + testWidgets("When calling `navigateToDeposit` without groups, it should not pass it to the query", (tester) async { + runApp(material); + + const token0 = "0x789"; + const token1 = "0xabc"; + const network = AppNetworks.mainnet; + + await ZupNavigator().navigateToDeposit( + group0: null, + group1: null, + token0: token0, + token1: token1, + network: network, + ); + + expect(Routefly.query.params, { + ZupDepositRouteParamsNames().token0: token0, + ZupDepositRouteParamsNames().token1: token1, + ZupDepositRouteParamsNames().network: network.name, + }); + }); + + testWidgets("When calling `navigateToDeposit` without token0, it should not pass it to the query", (tester) async { + runApp(material); + const group0 = "0x123"; + const group1 = "0x456"; + const token1 = "0xabc"; + const network = AppNetworks.mainnet; + + await ZupNavigator().navigateToDeposit( + group0: group0, + group1: group1, + token0: null, + token1: token1, + network: network, + ); + + expect(Routefly.query.params, { + ZupDepositRouteParamsNames().group0: group0, + ZupDepositRouteParamsNames().group1: group1, + ZupDepositRouteParamsNames().token1: token1, + ZupDepositRouteParamsNames().network: network.name, + }); + }); + + testWidgets("When calling `navigateToDeposit` without token1, it should not pass it to the query", (tester) async { + runApp(material); + const group0 = "0x123"; + const group1 = "0x456"; + const token0 = "0xabc"; + const network = AppNetworks.mainnet; + + await ZupNavigator().navigateToDeposit( + group0: group0, + group1: group1, + token0: token0, + token1: null, + network: network, + ); + + expect(Routefly.query.params, { + ZupDepositRouteParamsNames().group0: group0, + ZupDepositRouteParamsNames().group1: group1, + ZupDepositRouteParamsNames().token0: token0, + ZupDepositRouteParamsNames().network: network.name, + }); + }); + + testWidgets("When calling `navigateToDeposit` without tokens, it should not pass it to the query", (tester) async { + runApp(material); + const group0 = "0x123"; + const group1 = "0x456"; + const network = AppNetworks.mainnet; + + await ZupNavigator().navigateToDeposit( + group0: group0, + group1: group1, + token0: null, + token1: null, + network: network, + ); + + expect(Routefly.query.params, { + ZupDepositRouteParamsNames().group0: group0, + ZupDepositRouteParamsNames().group1: group1, + ZupDepositRouteParamsNames().network: network.name, + }); + }); } diff --git a/test/widgets/goldens/token_group_card_hover.png b/test/widgets/goldens/token_group_card_hover.png new file mode 100644 index 0000000..0d241ad Binary files /dev/null and b/test/widgets/goldens/token_group_card_hover.png differ diff --git a/test/widgets/goldens/token_group_card_hover_info.png b/test/widgets/goldens/token_group_card_hover_info.png new file mode 100644 index 0000000..9d24364 Binary files /dev/null and b/test/widgets/goldens/token_group_card_hover_info.png differ diff --git a/test/widgets/goldens/yield_card_24h.png b/test/widgets/goldens/yield_card_24h.png index 85315f1..0fc6d59 100644 Binary files a/test/widgets/goldens/yield_card_24h.png and b/test/widgets/goldens/yield_card_24h.png differ diff --git a/test/widgets/goldens/yield_card_30d.png b/test/widgets/goldens/yield_card_30d.png index 663cf12..066d0cf 100644 Binary files a/test/widgets/goldens/yield_card_30d.png and b/test/widgets/goldens/yield_card_30d.png differ diff --git a/test/widgets/goldens/yield_card_7d.png b/test/widgets/goldens/yield_card_7d.png new file mode 100644 index 0000000..c7a6a58 Binary files /dev/null and b/test/widgets/goldens/yield_card_7d.png differ diff --git a/test/widgets/goldens/yield_card_90d.png b/test/widgets/goldens/yield_card_90d.png index 695bbfe..b6f1127 100644 Binary files a/test/widgets/goldens/yield_card_90d.png and b/test/widgets/goldens/yield_card_90d.png differ diff --git a/test/widgets/goldens/yield_card_network_icon.png b/test/widgets/goldens/yield_card_network_icon.png index cd09bcb..3a8d396 100644 Binary files a/test/widgets/goldens/yield_card_network_icon.png and b/test/widgets/goldens/yield_card_network_icon.png differ diff --git a/test/widgets/goldens/yield_card_selected.png b/test/widgets/goldens/yield_card_selected.png index a648b35..af02700 100644 Binary files a/test/widgets/goldens/yield_card_selected.png and b/test/widgets/goldens/yield_card_selected.png differ diff --git a/test/widgets/goldens/yield_card_tooltip_protocol.png b/test/widgets/goldens/yield_card_tooltip_protocol.png new file mode 100644 index 0000000..1df6275 Binary files /dev/null and b/test/widgets/goldens/yield_card_tooltip_protocol.png differ diff --git a/test/widgets/token_group_card_test.dart b/test/widgets/token_group_card_test.dart new file mode 100644 index 0000000..481cbac --- /dev/null +++ b/test/widgets/token_group_card_test.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/widgets/token_group_card.dart'; +import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/zup_core.dart'; +import 'package:zup_ui_kit/zup_tooltip.dart'; + +import '../golden_config.dart'; +import '../mocks.dart'; + +void main() { + setUp(() { + inject.registerFactory(() => mockZupCachedImage()); + }); + + tearDown(() => inject.reset()); + + Future goldenBuilder({TokenGroupDto? group, VoidCallback? onClick}) async => await goldenDeviceBuilder( + SizedBox( + width: 300, + child: Center( + child: TokenGroupCard(group: group ?? TokenGroupDto.fixture(), onClick: onClick ?? () {}), + ), + ), + ); + + zGoldenTest("When clicking the card, the onClick callback should be called", (tester) async { + bool callbackCalled = false; + await tester.pumpDeviceBuilder(await goldenBuilder(onClick: () => callbackCalled = true)); + + await tester.tap(find.byType(TokenGroupCard)); + await tester.pumpAndSettle(); + + expect(callbackCalled, true); + }); + + zGoldenTest("When hovering the card, it should show the hover state", goldenFileName: "token_group_card_hover", ( + tester, + ) async { + await tester.pumpDeviceBuilder(await goldenBuilder()); + + await tester.hover(find.byType(TokenGroupCard)); + await tester.pumpAndSettle(); + }); + + zGoldenTest( + "When hovering the info icon, it should show a tooltip with all the tokens that the group has", + goldenFileName: "token_group_card_hover_info", + (tester) async { + final group = TokenGroupDto.fixture().copyWith( + tokens: List.generate(20, (index) => TokenDto.fixture().copyWith(symbol: "Token$index")), + ); + await tester.pumpDeviceBuilder(await goldenBuilder(group: group)); + + await tester.hover(find.byType(ZupTooltip)); + await tester.pumpAndSettle(); + }, + ); +} diff --git a/test/widgets/token_selector_button/goldens/token_selector_button_click.png b/test/widgets/token_selector_button/goldens/token_selector_button_click.png index ede1768..b6b7ff7 100644 Binary files a/test/widgets/token_selector_button/goldens/token_selector_button_click.png and b/test/widgets/token_selector_button/goldens/token_selector_button_click.png differ diff --git a/test/widgets/token_selector_button/goldens/token_selector_button_click_mobile.png b/test/widgets/token_selector_button/goldens/token_selector_button_click_mobile.png index e2f2555..b94d983 100644 Binary files a/test/widgets/token_selector_button/goldens/token_selector_button_click_mobile.png and b/test/widgets/token_selector_button/goldens/token_selector_button_click_mobile.png differ diff --git a/test/widgets/token_selector_button/goldens/token_selector_button_selection_token_group.png b/test/widgets/token_selector_button/goldens/token_selector_button_selection_token_group.png new file mode 100644 index 0000000..46b5b61 Binary files /dev/null and b/test/widgets/token_selector_button/goldens/token_selector_button_selection_token_group.png differ diff --git a/test/widgets/token_selector_button/token_selector_button_controller_test.dart b/test/widgets/token_selector_button/token_selector_button_controller_test.dart index fbdc9ee..e30d3c9 100644 --- a/test/widgets/token_selector_button/token_selector_button_controller_test.dart +++ b/test/widgets/token_selector_button/token_selector_button_controller_test.dart @@ -1,5 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; import 'package:zup_app/widgets/token_selector_button/token_selector_button_controller.dart'; void main() { @@ -9,13 +10,6 @@ void main() { sut = TokenSelectorButtonController(); }); - test("When passing the initialSelected token, it should set the variable", () { - final initialSelectedToken = TokenDto.fixture(); - sut = TokenSelectorButtonController(initialSelectedToken: initialSelectedToken); - - expect(sut.selectedToken.hashCode, initialSelectedToken.hashCode); - }); - test("When changing the selected token by calling the function, it should set the variable", () { final newSelectedToken = TokenDto.fixture(); @@ -36,8 +30,51 @@ void main() { }); test("When changing the selected token to null, it should emit a null event", () { - expectLater(sut.selectedTokenStream, emits(null)); + sut.changeToken(TokenDto.fixture()); + expectLater(sut.selectedTokenStream, emits(null)); sut.changeToken(null); }); + + test("When there is a selected group, and a call to 'changeToken' is made, it should remove the selected group", () { + sut.changeTokenGroup(TokenGroupDto.fixture()); + sut.changeToken(TokenDto.fixture()); + + expect(sut.selectedTokenGroup, null); + }); + + test("When there is a selected group, and a call to 'changeToken' is made, it should emit null selected group", () { + sut.changeTokenGroup(TokenGroupDto.fixture()); + + expectLater(sut.selectedTokenGroupStream, emits(null)); + sut.changeToken(TokenDto.fixture()); + }); + + test("When calling 'changeTokenGroup' with a previous selected token, it should remove the selected token", () { + sut.changeToken(TokenDto.fixture()); + sut.changeTokenGroup(TokenGroupDto.fixture()); + + expect(sut.selectedToken, null); + }); + + test("When calling 'changeTokenGroup' with a previous selected token, it should emit null selected token", () { + sut.changeToken(TokenDto.fixture()); + + expectLater(sut.selectedTokenStream, emits(null)); + sut.changeTokenGroup(TokenGroupDto.fixture()); + }); + + test("When calling 'changeTokenGroup' with a new token group, it should emit the new token group", () { + final newTokenGroup = TokenGroupDto.fixture(); + + expectLater(sut.selectedTokenGroupStream, emits(newTokenGroup)); + sut.changeTokenGroup(newTokenGroup); + }); + + test("When calling 'changeTokenGroup' with a new token group, it should set the new token group", () { + final newTokenGroup = TokenGroupDto.fixture(); + + sut.changeTokenGroup(newTokenGroup); + expect(sut.selectedTokenGroup.hashCode, newTokenGroup.hashCode); + }); } diff --git a/test/widgets/token_selector_button/token_selector_button_test.dart b/test/widgets/token_selector_button/token_selector_button_test.dart index fb380e1..2c3a9b7 100644 --- a/test/widgets/token_selector_button/token_selector_button_test.dart +++ b/test/widgets/token_selector_button/token_selector_button_test.dart @@ -5,11 +5,12 @@ import 'package:mocktail/mocktail.dart'; import 'package:web3kit/web3kit.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/debouncer.dart'; -import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_list_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; import 'package:zup_app/widgets/token_card.dart'; +import 'package:zup_app/widgets/token_group_card.dart'; import 'package:zup_app/widgets/token_selector_button/token_selector_button.dart'; import 'package:zup_app/widgets/token_selector_button/token_selector_button_controller.dart'; import 'package:zup_app/widgets/token_selector_modal/token_selector_modal_cubit.dart'; @@ -38,7 +39,7 @@ void main() { inject.registerFactory(() => appCubit); inject.registerLazySingleton(() => Debouncer(milliseconds: 0)); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => [TokenDto.fixture()]); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto.fixture()); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); }); @@ -51,34 +52,21 @@ void main() { device: isMobile ? GoldenDevice.mobile : GoldenDevice.square, ); - zGoldenTest("When the initialSelectedToken is not null in the controller, it should show the selected token", - goldenFileName: "token_selector_button_initial_selected_token", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder(TokenSelectorButtonController(initialSelectedToken: TokenDto.fixture())), - ); - - await tester.pumpAndSettle(); - }); - - zGoldenTest("When the initialSelectedToken is null in the controller, it should show the state to select token", - goldenFileName: "token_selector_button_initial_selected_token_null", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder(TokenSelectorButtonController(initialSelectedToken: null)), - ); - - await tester.pumpAndSettle(); - }); - - zGoldenTest("When pressing the button, it show the modal to select tokens", - goldenFileName: "token_selector_button_click", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder(TokenSelectorButtonController()), - wrapper: GoldenConfig.localizationsWrapper()); + zGoldenTest( + "When pressing the button, it show the modal to select tokens", + goldenFileName: "token_selector_button_click", + (tester) async { + await tester.pumpDeviceBuilder( + await goldenBuilder(TokenSelectorButtonController()), + wrapper: GoldenConfig.localizationsWrapper(), + ); - await tester.pumpAndSettle(); - await tester.tap(find.byType(TokenSelectorButton)); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenSelectorButton)); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( """When pressing the button in a mobile-size device, @@ -86,41 +74,64 @@ void main() { goldenFileName: "token_selector_button_click_mobile", (tester) async { await tester.pumpDeviceBuilder( - await goldenBuilder( - TokenSelectorButtonController(), - isMobile: true, - ), - wrapper: GoldenConfig.localizationsWrapper()); + await goldenBuilder(TokenSelectorButtonController(), isMobile: true), + wrapper: GoldenConfig.localizationsWrapper(), + ); + + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenSelectorButton)); + + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + "When selecting a token in the modal, it should update the button state to selected", + goldenFileName: "token_selector_button_selection", + (tester) async { + await tester.pumpDeviceBuilder( + await goldenBuilder(TokenSelectorButtonController()), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); await tester.tap(find.byType(TokenSelectorButton)); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenCard).first); await tester.pumpAndSettle(); }, ); - zGoldenTest("When selecting a token in the modal, it should update the button state to selected", - goldenFileName: "token_selector_button_selection", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder(TokenSelectorButtonController()), - wrapper: GoldenConfig.localizationsWrapper()); + zGoldenTest( + """When selecting a token group in the modal, it should update the button + state to selected with the token group details""", + goldenFileName: "token_selector_button_selection_token_group", + (tester) async { + await tester.pumpDeviceBuilder( + await goldenBuilder(TokenSelectorButtonController()), + wrapper: GoldenConfig.localizationsWrapper(), + ); - await tester.pumpAndSettle(); - await tester.tap(find.byType(TokenSelectorButton)); - await tester.pumpAndSettle(); - await tester.tap(find.byType(TokenCard).first); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenSelectorButton)); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TokenGroupCard).first); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }, + ); - zGoldenTest("When hovering the button, it should show the hover state", goldenFileName: "token_selector_button_hover", - (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder( - TokenSelectorButtonController(), - )); + zGoldenTest( + "When hovering the button, it should show the hover state", + goldenFileName: "token_selector_button_hover", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(TokenSelectorButtonController())); - await tester.pumpAndSettle(); - await tester.hover(find.byType(TokenSelectorButton)); + await tester.pumpAndSettle(); + await tester.hover(find.byType(TokenSelectorButton)); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + }, + ); } diff --git a/test/widgets/token_selector_modal/goldens/token_selector_modal_all_networks.png b/test/widgets/token_selector_modal/goldens/token_selector_modal_all_networks.png index b99562e..5b1a24f 100644 Binary files a/test/widgets/token_selector_modal/goldens/token_selector_modal_all_networks.png and b/test/widgets/token_selector_modal/goldens/token_selector_modal_all_networks.png differ diff --git a/test/widgets/token_selector_modal/goldens/token_selector_modal_success.png b/test/widgets/token_selector_modal/goldens/token_selector_modal_success.png index b9eb004..38f24ce 100644 Binary files a/test/widgets/token_selector_modal/goldens/token_selector_modal_success.png and b/test/widgets/token_selector_modal/goldens/token_selector_modal_success.png differ diff --git a/test/widgets/token_selector_modal/goldens/token_selector_modal_token_groups_tooltip.png b/test/widgets/token_selector_modal/goldens/token_selector_modal_token_groups_tooltip.png new file mode 100644 index 0000000..d8ccb7f Binary files /dev/null and b/test/widgets/token_selector_modal/goldens/token_selector_modal_token_groups_tooltip.png differ diff --git a/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart b/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart index 0ac65fb..6d0227a 100644 --- a/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart +++ b/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart @@ -4,6 +4,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:web3kit/web3kit.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_list_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; import 'package:zup_app/widgets/token_selector_modal/token_selector_modal_cubit.dart'; @@ -25,18 +26,18 @@ void main() { sut = TokenSelectorModalCubit(tokensRepository, appCubit, wallet); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => [TokenDto.fixture()]); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto.fixture()); when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => []); }); test( - "Whe calling `fetchTokenList` and it has already been called in the same network, it should return the cached token list", - () async { - final tokenList = [TokenDto.fixture()]; + "Whe calling `fetchTokenList` and it has already been called in the same network, it should return the cached token list", + () async { + final tokenList = TokenListDto.fixture(); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokenList); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => tokenList); - expectLater( + expectLater( sut.stream, emitsInOrder([ const TokenSelectorModalState.loading(), @@ -46,103 +47,114 @@ void main() { // expected to emit the cached token list TokenSelectorModalState.success(tokenList), - ])); + ]), + ); - await sut.fetchTokenList(); + await sut.fetchTokenList(); - // update the state to make it emit the cached token list again - await sut.searchToken(""); + // update the state to make it emit the cached token list again + await sut.searchToken(""); - await sut.fetchTokenList(); + await sut.fetchTokenList(); - verify(() => tokensRepository.getPopularTokens(any())).called(1); - }); + verify(() => tokensRepository.getTokenList(any())).called(1); + }, + ); test( - "When calling `fetchTokenList` and it has not already been called yet in the current network, it should get the tokens list again", - () async { - final tokenList1 = [TokenDto.fixture()]; - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokenList1); - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); + "When calling `fetchTokenList` and it has not already been called yet in the current network, it should get the tokens list again", + () async { + final tokenList1 = TokenListDto.fixture(); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => tokenList1); + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - await sut.fetchTokenList(); + await sut.fetchTokenList(); - final tokenList2 = [TokenDto.fixture()]; - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokenList2); - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.mainnet); + final tokenList2 = TokenListDto.fixture(); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => tokenList2); + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.mainnet); - await sut.fetchTokenList(); + await sut.fetchTokenList(); - expect((await sut.tokenList).hashCode, tokenList2.hashCode); - verify(() => tokensRepository.getPopularTokens(any())).called(2); - }); + expect((await sut.tokenList).hashCode, tokenList2.hashCode); + verify(() => tokensRepository.getTokenList(any())).called(2); + }, + ); test( - "When calling `fetchTokenList` switching networks, and the current network has already been called, it should return the cached list ", - () async { - final tokenList1 = [TokenDto.fixture()]; - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokenList1); - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); + "When calling `fetchTokenList` switching networks, and the current network has already been called, it should return the cached list ", + () async { + final tokenList1 = TokenListDto.fixture(); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => tokenList1); + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - await sut.fetchTokenList(); + await sut.fetchTokenList(); - final tokenList2 = [TokenDto.fixture()]; - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokenList2); - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.mainnet); + final tokenList2 = TokenListDto.fixture(); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => tokenList2); + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.mainnet); - await sut.fetchTokenList(); + await sut.fetchTokenList(); - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokenList1); - await sut.fetchTokenList(); + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => tokenList1); + await sut.fetchTokenList(); - expect((await sut.tokenList).hashCode, tokenList1.hashCode); - verify(() => tokensRepository.getPopularTokens(any())).called(2); - }); + expect((await sut.tokenList).hashCode, tokenList1.hashCode); + verify(() => tokensRepository.getTokenList(any())).called(2); + }, + ); - test("""When calling `fetchTokenList` and right after calling + test( + """When calling `fetchTokenList` and right after calling another function that will change the state before the future to get the list of tokens completes, it should not emit the state of loaded, but should update the - cached list""", () async { - const requestDuration = Duration(milliseconds: 1); - final tokenList = [TokenDto.fixture()]; + cached list""", + () async { + const requestDuration = Duration(milliseconds: 1); + final tokenList = TokenListDto.fixture(); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer( - (_) async => Future.delayed(requestDuration, () => tokenList), - ); + when( + () => tokensRepository.getTokenList(any()), + ).thenAnswer((_) async => Future.delayed(requestDuration, () => tokenList)); - expectLater(sut.stream, neverEmits(TokenSelectorModalState.success(tokenList))); + expectLater(sut.stream, neverEmits(TokenSelectorModalState.success(tokenList))); - sut.fetchTokenList(); - await Future.delayed(Duration.zero); - await sut.searchToken(""); // update the state before the future completes + sut.fetchTokenList(); + await Future.delayed(Duration.zero); + await sut.searchToken(""); // update the state before the future completes - await Future.delayed(requestDuration); + await Future.delayed(requestDuration); - sut.close(); // should not emit a new state, so we close it to make the test fail if it does - }); + sut.close(); // should not emit a new state, so we close it to make the test fail if it does + }, + ); - test("""When calling `fetchTokenList` and right after calling + test( + """When calling `fetchTokenList` and right after calling another function that will change the state before the future to get the list of tokens completes, it should not emit the List loaded state, and if it completes with - error, it should not update the cached list or emit error state""", () async { - const requestDuration = Duration(milliseconds: 1); + error, it should not update the cached list or emit error state""", + () async { + const requestDuration = Duration(milliseconds: 1); - when(() => tokensRepository.getPopularTokens(any())) - .thenAnswer((_) => Future.delayed(requestDuration, () => throw "dale")); + when( + () => tokensRepository.getTokenList(any()), + ).thenAnswer((_) => Future.delayed(requestDuration, () => throw "dale")); - sut.fetchTokenList(); - await sut.searchToken(""); // update the state before the future completes + sut.fetchTokenList(); + await sut.searchToken(""); // update the state before the future completes - await Future.delayed(requestDuration); + await Future.delayed(requestDuration); - expect(sut.state != const TokenSelectorModalState.error(), true); - }); + expect(sut.state != const TokenSelectorModalState.error(), true); + }, + ); test("when calling load data, and the repository throws an error, it should emit an error state", () async { - when(() => tokensRepository.getPopularTokens(any())).thenThrow("dale"); + when(() => tokensRepository.getTokenList(any())).thenThrow("dale"); await sut.fetchTokenList(); @@ -150,9 +162,9 @@ void main() { }); test("when calling load data, and the repository returns success, it should emit the success state", () async { - final tokenList = [TokenDto.fixture()]; + final tokenList = TokenListDto.fixture(); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => tokenList); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => tokenList); await sut.fetchTokenList(); @@ -169,140 +181,167 @@ void main() { verify(() => tokensRepository.searchToken(query, any())).called(1); }); - test("""When calling `searchTokens` and right after call another function + test( + """When calling `searchTokens` and right after call another function that changes the state before the future to get the search results completes - it should not emit the searchSuccess state when it completes""", () async { - const requestDuration = Duration(milliseconds: 1); - final futureResult = []; + it should not emit the searchSuccess state when it completes""", + () async { + const requestDuration = Duration(milliseconds: 1); + final futureResult = []; - when(() => tokensRepository.searchToken(any(), any())).thenAnswer( - (_) => Future.delayed(requestDuration, () => futureResult), - ); + when( + () => tokensRepository.searchToken(any(), any()), + ).thenAnswer((_) => Future.delayed(requestDuration, () => futureResult)); - expectLater(sut.stream, neverEmits(TokenSelectorModalState.searchSuccess(futureResult))); - sut.searchToken("dale"); - await sut.fetchTokenList(); + expectLater(sut.stream, neverEmits(TokenSelectorModalState.searchSuccess(futureResult))); + sut.searchToken("dale"); + await sut.fetchTokenList(); - await Future.delayed(requestDuration); + await Future.delayed(requestDuration); - sut.close(); - }); + sut.close(); + }, + ); - test("""When calling `searchTokens` and right after call another function + test( + """When calling `searchTokens` and right after call another function that changes the state before the future to get the search results completes - it should not emit the searchError state when it completes with an error""", () async { - const requestDuration = Duration(milliseconds: 1); - const searchQuery = "dale"; + it should not emit the searchError state when it completes with an error""", + () async { + const requestDuration = Duration(milliseconds: 1); + const searchQuery = "dale"; - when(() => tokensRepository.searchToken(any(), any())) - .thenAnswer((_) => Future.delayed(requestDuration, () => throw "error")); + when( + () => tokensRepository.searchToken(any(), any()), + ).thenAnswer((_) => Future.delayed(requestDuration, () => throw "error")); - expectLater(sut.stream, neverEmits(const TokenSelectorModalState.searchError(searchQuery))); - sut.searchToken(searchQuery); - await sut.fetchTokenList(); + expectLater(sut.stream, neverEmits(const TokenSelectorModalState.searchError(searchQuery))); + sut.searchToken(searchQuery); + await sut.fetchTokenList(); - await Future.delayed(requestDuration); + await Future.delayed(requestDuration); - sut.close(); - }); + sut.close(); + }, + ); - test("When calling `searchTokens` and the repository returns success it should emit the searchSuccess state", - () async { - final searchResult = [TokenDto.fixture(), TokenDto.fixture()]; - when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => searchResult); + test( + "When calling `searchTokens` and the repository returns success it should emit the searchSuccess state", + () async { + final searchResult = [TokenDto.fixture(), TokenDto.fixture()]; + when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => searchResult); - await sut.searchToken("dale"); + await sut.searchToken("dale"); - expect(sut.state, TokenSelectorModalState.searchSuccess(searchResult)); - }); + expect(sut.state, TokenSelectorModalState.searchSuccess(searchResult)); + }, + ); - test("When calling `searchTokens` and the repository throw an generic error, it should emit the searchError state", - () async { - const searchQuery = "dale"; + test( + "When calling `searchTokens` and the repository throw an generic error, it should emit the searchError state", + () async { + const searchQuery = "dale"; - when(() => tokensRepository.searchToken(any(), any())).thenThrow("some error"); + when(() => tokensRepository.searchToken(any(), any())).thenThrow("some error"); - await sut.searchToken(searchQuery); + await sut.searchToken(searchQuery); - expect(sut.state, const TokenSelectorModalState.searchError(searchQuery)); - }); + expect(sut.state, const TokenSelectorModalState.searchError(searchQuery)); + }, + ); - test("When calling `searchTokens` and the repository returns a empty list, it should emit the search not found state", - () async { - const searchQuery = "dale"; + test( + "When calling `searchTokens` and the repository returns a empty list, it should emit the search not found state", + () async { + const searchQuery = "dale"; - when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => []); + when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => []); - await sut.searchToken(searchQuery); + await sut.searchToken(searchQuery); - expect(sut.state, const TokenSelectorModalState.searchNotFound(searchQuery)); - }); + expect(sut.state, const TokenSelectorModalState.searchNotFound(searchQuery)); + }, + ); - test("""When calling `searchTokens` and the repository throw an error, + test( + """When calling `searchTokens` and the repository throw an error, but the error is because the dio request have been canceled, it should keep - the state as the last one""", () async { - const searchQuery = "dale"; + the state as the last one""", + () async { + const searchQuery = "dale"; - when(() => tokensRepository.searchToken(any(), any())).thenThrow(DioException( - requestOptions: RequestOptions(path: ""), - type: DioExceptionType.cancel, - )); + when(() => tokensRepository.searchToken(any(), any())).thenThrow( + DioException( + requestOptions: RequestOptions(path: ""), + type: DioExceptionType.cancel, + ), + ); - expectLater(sut.stream, neverEmits(const TokenSelectorModalState.searchError(searchQuery))); - await sut.searchToken(searchQuery); + expectLater(sut.stream, neverEmits(const TokenSelectorModalState.searchError(searchQuery))); + await sut.searchToken(searchQuery); - expect(sut.state, const TokenSelectorModalState.searchLoading()); - await sut.close(); - }); - - test("When calling `fetchTokenList` with 'forceRefresh' true, it should refetch the token list ignoring the cache", - () async { - await sut.fetchTokenList(forceRefresh: true); - await sut.fetchTokenList(forceRefresh: true); - await sut.fetchTokenList(forceRefresh: true); - - verify(() => tokensRepository.getPopularTokens(any())).called(3); - }); + expect(sut.state, const TokenSelectorModalState.searchLoading()); + await sut.close(); + }, + ); test( - "When callling 'searchToken' with a valid ethereum address, and the network is all networks, it should emit the search not found state", - () async { - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + "When calling `fetchTokenList` with 'forceRefresh' true, it should refetch the token list ignoring the cache", + () async { + await sut.fetchTokenList(forceRefresh: true); + await sut.fetchTokenList(forceRefresh: true); + await sut.fetchTokenList(forceRefresh: true); - const address = "0x0000000000000000000000000000000000000000"; - await sut.searchToken(address); - - expect(sut.state, const TokenSelectorModalState.searchNotFound(address)); - verifyNever(() => tokensRepository.searchToken(any(), any())); - }); + verify(() => tokensRepository.getTokenList(any())).called(3); + }, + ); - test("""When calling 'searchToken' and all the tokens in the list returned does not have name and symbol, - it should emit the search not found state""", () async { - final returnedList = [ - TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), - TokenDto.fixture().copyWith(name: "", symbol: "", decimals: {}, logoUrl: "", addresses: {}), - ]; + test( + "When callling 'searchToken' with a valid ethereum address, and the network is all networks, it should emit the search not found state", + () async { + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => returnedList); + const address = "0x0000000000000000000000000000000000000000"; + await sut.searchToken(address); - await sut.searchToken("dale"); + expect(sut.state, const TokenSelectorModalState.searchNotFound(address)); + verifyNever(() => tokensRepository.searchToken(any(), any())); + }, + ); - expect(sut.state, const TokenSelectorModalState.searchNotFound("dale")); - }); + test( + """When calling 'searchToken' and all the tokens in the list returned does not have name and symbol, + it should emit the search not found state""", + () async { + final returnedList = [ + TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), + TokenDto.fixture().copyWith(name: "", symbol: "", decimals: {}, logoUrl: "", addresses: {}), + ]; - test("""When calling 'searchToken' and one token in the list returned has symbol and name, - it should emit the search sucesss state, without the tokens without name and symbol""", () async { - final namedToken = TokenDto.fixture(); - final returnedList = [ - TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), - TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), - namedToken, - ]; + when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => returnedList); - when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => returnedList); + await sut.searchToken("dale"); - await sut.searchToken("dale"); + expect(sut.state, const TokenSelectorModalState.searchNotFound("dale")); + }, + ); - expect(sut.state, TokenSelectorModalState.searchSuccess([namedToken])); - }); + test( + """When calling 'searchToken' and one token in the list returned has symbol and name, + it should emit the search sucesss state, without the tokens without name and symbol""", + () async { + final namedToken = TokenDto.fixture(); + final returnedList = [ + TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), + TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), + namedToken, + ]; + + when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => returnedList); + + await sut.searchToken("dale"); + + expect(sut.state, TokenSelectorModalState.searchSuccess([namedToken])); + }, + ); } diff --git a/test/widgets/token_selector_modal/token_selector_modal_test.dart b/test/widgets/token_selector_modal/token_selector_modal_test.dart index 04c5a5b..0c611e2 100644 --- a/test/widgets/token_selector_modal/token_selector_modal_test.dart +++ b/test/widgets/token_selector_modal/token_selector_modal_test.dart @@ -5,12 +5,17 @@ import 'package:mocktail/mocktail.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/debouncer.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/token_group_dto.dart'; +import 'package:zup_app/core/dtos/token_list_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; +import 'package:zup_app/widgets/token_group_card.dart'; import 'package:zup_app/widgets/token_selector_modal/token_selector_modal.dart'; import 'package:zup_app/widgets/token_selector_modal/token_selector_modal_cubit.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/zup_core.dart'; +import 'package:zup_ui_kit/zup_ui_kit.dart'; import '../../golden_config.dart'; import '../../mocks.dart'; @@ -32,7 +37,7 @@ void main() { inject.registerFactory(() => appCubit); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - when(() => tokensRepository.getPopularTokens(any())).thenAnswer((_) async => [TokenDto.fixture()]); + when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto.fixture()); when(() => cubit.stream).thenAnswer((_) => const Stream.empty()); when(() => cubit.state).thenReturn(const TokenSelectorModalState.initial()); when(() => cubit.fetchTokenList()).thenAnswer((_) async {}); @@ -41,19 +46,25 @@ void main() { tearDown(() => inject.reset()); - Future goldenBuilder({Function(TokenDto)? onSelectToken}) async => await goldenDeviceBuilder( - Builder(builder: (context) { - WidgetsBinding.instance.addPostFrameCallback((_) { - TokenSelectorModal.show( - context, - onSelectToken: onSelectToken ?? (token) {}, - showAsBottomSheet: false, - ); - }); - - return const SizedBox(); - }), - ); + Future goldenBuilder({ + Function(TokenDto)? onSelectToken, + Function(TokenGroupDto)? onSelectTokenGroup, + }) async => await goldenDeviceBuilder( + Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback((_) { + TokenSelectorModal.show( + context, + onSelectToken: onSelectToken ?? (token) {}, + showAsBottomSheet: false, + onSelectTokenGroup: onSelectTokenGroup ?? (token) {}, + ); + }); + + return const SizedBox(); + }, + ), + ); zGoldenTest("When initializing the modal, it should immediately load the token list", (tester) async { await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); @@ -91,8 +102,9 @@ void main() { verify(() => cubit.searchToken(search)).called(1); }); - zGoldenTest("When cleaning the search query, it should call load data from cubit, in order to reset the state", - (tester) async { + zGoldenTest("When cleaning the search query, it should call load data from cubit, in order to reset the state", ( + tester, + ) async { when(() => cubit.searchToken(any())).thenAnswer((_) async {}); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); @@ -104,13 +116,16 @@ void main() { verify(() => cubit.fetchTokenList()).called(2); // 2 because of the initial one }); - zGoldenTest("When the error state is emitted, it should show the error state", - goldenFileName: "token_selector_modal_error_state", (tester) async { - when(() => cubit.state).thenReturn(const TokenSelectorModalState.error()); + zGoldenTest( + "When the error state is emitted, it should show the error state", + goldenFileName: "token_selector_modal_error_state", + (tester) async { + when(() => cubit.state).thenReturn(const TokenSelectorModalState.error()); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); - }); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + }, + ); zGoldenTest("When clicking the helper button of the error state, it should get again the token list", (tester) async { when(() => cubit.state).thenReturn(const TokenSelectorModalState.error()); @@ -124,18 +139,21 @@ void main() { verify(() => cubit.fetchTokenList()).called(2); // 2 because of the initial one }); - zGoldenTest("When the search error is emitted, it should show the search error state", - goldenFileName: "token_selector_modal_search_error_state", (tester) async { - const searchedTerm = "dale search"; + zGoldenTest( + "When the search error is emitted, it should show the search error state", + goldenFileName: "token_selector_modal_search_error_state", + (tester) async { + const searchedTerm = "dale search"; - when(() => cubit.state).thenReturn(const TokenSelectorModalState.searchError(searchedTerm)); + when(() => cubit.state).thenReturn(const TokenSelectorModalState.searchError(searchedTerm)); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("search-token-field")), searchedTerm); - await tester.pumpAndSettle(); - }); + await tester.enterText(find.byKey(const Key("search-token-field")), searchedTerm); + await tester.pumpAndSettle(); + }, + ); zGoldenTest("When clicking the helper button of the search error state, it should try search again", (tester) async { const searchTerm = "dale search"; @@ -154,82 +172,133 @@ void main() { verify(() => cubit.searchToken(searchTerm)).called(2); // 2 because of the initial one }); - zGoldenTest("When the state is search not found, it should show the not found state", - goldenFileName: "token_selector_modal_search_not_found", (tester) async { - const searchTerm = "dale search"; - - when(() => cubit.state).thenReturn(const TokenSelectorModalState.searchNotFound(searchTerm)); - - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); - }); + zGoldenTest( + "When the state is search not found, it should show the not found state", + goldenFileName: "token_selector_modal_search_not_found", + (tester) async { + const searchTerm = "dale search"; - zGoldenTest("When the state search loading is emitted, it should show the loading state", - goldenFileName: "token_selector_modal_search_loading", (tester) async { - when(() => cubit.state).thenReturn(const TokenSelectorModalState.searchLoading()); + when(() => cubit.state).thenReturn(const TokenSelectorModalState.searchNotFound(searchTerm)); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); - }); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + }, + ); - zGoldenTest("When the state is search success, it should show the search success state", - goldenFileName: "token_selector_modal_search_success", (tester) async { - final tokenList = List.generate(10, (_) => TokenDto.fixture()); + zGoldenTest( + "When the state search loading is emitted, it should show the loading state", + goldenFileName: "token_selector_modal_search_loading", + (tester) async { + when(() => cubit.state).thenReturn(const TokenSelectorModalState.searchLoading()); - when(() => cubit.state).thenReturn(TokenSelectorModalState.searchSuccess(tokenList)); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + }, + ); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); - }); - - zGoldenTest("When the state is loading, it should show the loading state", - goldenFileName: "token_selector_modal_loading", (tester) async { - when(() => cubit.state).thenReturn(const TokenSelectorModalState.loading()); + zGoldenTest( + "When the state is search success, it should show the search success state", + goldenFileName: "token_selector_modal_search_success", + (tester) async { + final tokenList = List.generate(10, (_) => TokenDto.fixture()); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); - }); + when(() => cubit.state).thenReturn(TokenSelectorModalState.searchSuccess(tokenList)); - zGoldenTest("When the state is success, it should show the success state", - goldenFileName: "token_selector_modal_success", (tester) async { - final tokenList = [TokenDto.fixture()]; + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + }, + ); - when(() => cubit.state).thenReturn(TokenSelectorModalState.success(tokenList)); + zGoldenTest( + "When the state is loading, it should show the loading state", + goldenFileName: "token_selector_modal_loading", + (tester) async { + when(() => cubit.state).thenReturn(const TokenSelectorModalState.loading()); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); - }); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + }, + ); - zGoldenTest("When clicking the token card in the `Popular Tokens` section, it should callback with selected token", - (tester) async { - final tokenList = [TokenDto.fixture()]; + zGoldenTest( + "When the state is success, it should show the success state", + goldenFileName: "token_selector_modal_success", + (tester) async { + final tokenList = TokenListDto.fixture(); + + when(() => cubit.state).thenReturn(TokenSelectorModalState.success(tokenList)); + + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest("When clicking the token card in the `Popular Tokens` section, it should callback with selected token", ( + tester, + ) async { + final tokenList = TokenListDto.fixture(); TokenDto? selectedToken; when(() => cubit.state).thenReturn(TokenSelectorModalState.success(tokenList)); await tester.pumpDeviceBuilder( - await goldenBuilder( - onSelectToken: (token) { - return selectedToken = token; - }, - ), - wrapper: GoldenConfig.localizationsWrapper()); + await goldenBuilder( + onSelectToken: (token) { + return selectedToken = token; + }, + ), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("popular-token-0"))); await tester.pumpAndSettle(); - expect(selectedToken.hashCode, tokenList.first.hashCode); + expect(selectedToken.hashCode, tokenList.popularTokens.first.hashCode); }); zGoldenTest( - "When the network is all networks, it should show an alert about only being able to search by name or symbol", - goldenFileName: "token_selector_modal_all_networks", (tester) async { - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - when(() => cubit.state).thenReturn(TokenSelectorModalState.success([TokenDto.fixture()])); + "When the network is all networks, it should show an alert about only being able to search by name or symbol", + goldenFileName: "token_selector_modal_all_networks", + (tester) async { + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + when(() => cubit.state).thenReturn(TokenSelectorModalState.success(TokenListDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + "When hovering the tooltip of the token groups sector, it should display a message explaining it", + goldenFileName: "token_selector_modal_token_groups_tooltip", + (tester) async { + when(() => cubit.state).thenReturn(TokenSelectorModalState.success(TokenListDto.fixture())); + + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); + + await tester.hover(find.byType(ZupTooltip).first); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest("When selecting a token group, it should callback with the selected token group", (tester) async { + final tokenList = TokenListDto.fixture(); + TokenGroupDto? selectedTokenGroup; + + when(() => cubit.state).thenReturn(TokenSelectorModalState.success(tokenList)); + + await tester.pumpDeviceBuilder( + await goldenBuilder(onSelectTokenGroup: (tokenGroup) => selectedTokenGroup = tokenGroup), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); + + await tester.tap(find.byType(TokenGroupCard).first); + await tester.pumpAndSettle(); + + expect(selectedTokenGroup.hashCode, tokenList.tokenGroups.first.hashCode); }); } diff --git a/test/widgets/yield_card_test.dart b/test/widgets/yield_card_test.dart index 0028c7d..a1db2c3 100644 --- a/test/widgets/yield_card_test.dart +++ b/test/widgets/yield_card_test.dart @@ -1,13 +1,16 @@ -import 'package:flutter/src/widgets/basic.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/widgets/yield_card.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/zup_core.dart'; import '../golden_config.dart'; import '../mocks.dart'; @@ -18,9 +21,11 @@ void main() { setUp(() { appCubit = AppCubitMock(); + UrlLauncherPlatform.instance = UrlLauncherPlatformCustomMock(); inject.registerFactory(() => appCubit); inject.registerFactory(() => zupCachedImage); + inject.registerFactory(() => false, instanceName: InjectInstanceNames.infinityAnimationAutoPlay); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); when(() => appCubit.selectedNetworkStream).thenAnswer((_) => const Stream.empty()); @@ -33,37 +38,43 @@ void main() { Function(YieldDto? yield)? onChangeSelection, bool isSelected = false, YieldTimeFrame? timeFrame, - }) async => - await goldenDeviceBuilder(Center( - child: SizedBox( - width: 300, - child: Center( - child: YieldCard( - isSelected: isSelected, - onChangeSelection: (selectedYield) => onChangeSelection?.call(selectedYield), - timeFrame: timeFrame ?? YieldTimeFrame.day, - currentYield: currentYield ?? YieldDto.fixture(), - ), + }) async => await goldenDeviceBuilder( + Center( + child: SizedBox( + width: 300, + child: Center( + child: YieldCard( + isSelected: isSelected, + onChangeSelection: (selectedYield) => onChangeSelection?.call(selectedYield), + timeFrame: timeFrame ?? YieldTimeFrame.day, + currentYield: currentYield ?? YieldDto.fixture(), ), ), - )); - - zGoldenTest("When the app network is all networks, the yield card should display an icon of the network of the yield", - goldenFileName: "yield_card_network_icon", (tester) async { - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.allNetworks); + ), + ), + ); - await tester.pumpDeviceBuilder(await goldenBuilder()); + zGoldenTest( + "When the app network is all networks, the yield card should display an icon of the network of the yield", + goldenFileName: "yield_card_network_icon", + (tester) async { + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.allNetworks); - await tester.pumpAndSettle(); - }); + await tester.pumpDeviceBuilder(await goldenBuilder()); - zGoldenTest("When passing `isSelected` as true, the yield card should be selected", - goldenFileName: "yield_card_selected", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder(isSelected: true)); + await tester.pumpAndSettle(); + }, + ); - await tester.pumpAndSettle(); - }); + zGoldenTest( + "When passing `isSelected` as true, the yield card should be selected", + goldenFileName: "yield_card_selected", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(isSelected: true)); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( "When passing the 30d timeframe, the yield card should display the 30d yield", goldenFileName: "yield_card_30d", @@ -91,10 +102,47 @@ void main() { goldenFileName: "yield_card_90d", (tester) async { final currentYield = YieldDto.fixture().copyWith(yield90d: 1535421.32); - await tester - .pumpDeviceBuilder(await goldenBuilder(timeFrame: YieldTimeFrame.threeMonth, currentYield: currentYield)); + await tester.pumpDeviceBuilder( + await goldenBuilder(timeFrame: YieldTimeFrame.threeMonth, currentYield: currentYield), + ); await tester.pumpAndSettle(); }, ); + + zGoldenTest( + "When passing the 7d timeframe, the yield card should display the 7d yield", + goldenFileName: "yield_card_7d", + (tester) async { + final currentYield = YieldDto.fixture().copyWith(yield7d: 7987897687.32); + await tester.pumpDeviceBuilder(await goldenBuilder(timeFrame: YieldTimeFrame.week, currentYield: currentYield)); + + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + "When hovering the protocol name, it should show a tooltip to navigate to the protocol", + goldenFileName: "yield_card_tooltip_protocol", + (tester) async { + final currentYield = YieldDto.fixture(); + await tester.pumpDeviceBuilder(await goldenBuilder(currentYield: currentYield)); + + await tester.hover(find.text(currentYield.protocol.name)); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest("Clicking the tooltip to naviagte to the protocol, it should launch the protocol link", (tester) async { + final currentYield = YieldDto.fixture(); + await tester.pumpDeviceBuilder(await goldenBuilder(currentYield: currentYield)); + + await tester.hover(find.text(currentYield.protocol.name)); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("helper-button-tooltip"))); + await tester.pumpAndSettle(); + + expect(UrlLauncherPlatformCustomMock.lastLaunchedUrl, currentYield.protocol.url); + }); } diff --git a/web/index.html b/web/index.html index 92d4cee..e8684f0 100644 --- a/web/index.html +++ b/web/index.html @@ -61,6 +61,6 @@ inject(); - +