From f5a01063ed99e97e109882966cfe71eaf4cce10c Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Wed, 22 Apr 2026 18:26:17 -0300 Subject: [PATCH] feat(amm/ui): add pool position summary (#60) Fixes #60 --- amm-ui/qml/Main.qml | 25 +++- amm-ui/qml/components/EstimateInfoButton.qml | 64 +++++++++ amm-ui/qml/components/PoolPositionSummary.qml | 122 ++++++++++++++++++ amm-ui/qml/components/SummaryRow.qml | 60 +++++++++ amm-ui/qml/state/DummyPoolState.qml | 66 ++++++++++ 5 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 amm-ui/qml/components/EstimateInfoButton.qml create mode 100644 amm-ui/qml/components/PoolPositionSummary.qml create mode 100644 amm-ui/qml/components/SummaryRow.qml create mode 100644 amm-ui/qml/state/DummyPoolState.qml diff --git a/amm-ui/qml/Main.qml b/amm-ui/qml/Main.qml index 3ec1ce9..873eb02 100644 --- a/amm-ui/qml/Main.qml +++ b/amm-ui/qml/Main.qml @@ -1,6 +1,8 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import "components" +import "state" Item { id: root @@ -164,10 +166,31 @@ Item { } } - // ── Liquidity view (placeholder — replaced when LP branch merges) ───── + // ── Liquidity view ──────────────────────────────────────────────────── Item { anchors.fill: parent visible: navbar.currentIndex === 1 + + DummyPoolState { + id: poolState + } + + Rectangle { + anchors.fill: parent + color: "#151515" + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 10 + + PoolPositionSummary { + poolState: poolState + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight + } + } } } } diff --git a/amm-ui/qml/components/EstimateInfoButton.qml b/amm-ui/qml/components/EstimateInfoButton.qml new file mode 100644 index 0000000..1d3a109 --- /dev/null +++ b/amm-ui/qml/components/EstimateInfoButton.qml @@ -0,0 +1,64 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Button { + id: root + + property string helpText: qsTr("This value is derived from your LP token balance, total LP supply, and current pool reserves.") + + activeFocusOnTab: true + focusPolicy: Qt.StrongFocus + hoverEnabled: true + text: qsTr("?") + + Accessible.name: qsTr("Why this value is an estimate") + + onClicked: estimatePopup.opened ? estimatePopup.close() : estimatePopup.open() + + Keys.onEscapePressed: estimatePopup.close() + + contentItem: Text { + color: root.activeFocus || root.hovered || estimatePopup.opened ? "#F26A21" : "#E7E1D8" + font.bold: true + font.pixelSize: 11 + horizontalAlignment: Text.AlignHCenter + text: qsTr("i") + verticalAlignment: Text.AlignVCenter + } + + background: Rectangle { + border.color: root.activeFocus || estimatePopup.opened ? "#F26A21" : "#343434" + border.width: 1 + color: root.pressed ? "#2A221D" : "#1D1D1D" + radius: 8 + } + + Popup { + id: estimatePopup + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + focus: true + modal: false + padding: 10 + width: 224 + x: Math.max(-width + root.width, -196) + y: root.height + 4 + + onClosed: root.forceActiveFocus() + + background: Rectangle { + border.color: "#343434" + border.width: 1 + color: "#1D1D1D" + radius: 8 + } + + contentItem: Text { + color: "#E7E1D8" + font.pixelSize: 12 + lineHeight: 1.25 + text: root.helpText + wrapMode: Text.WordWrap + } + } +} diff --git a/amm-ui/qml/components/PoolPositionSummary.qml b/amm-ui/qml/components/PoolPositionSummary.qml new file mode 100644 index 0000000..c336f6e --- /dev/null +++ b/amm-ui/qml/components/PoolPositionSummary.qml @@ -0,0 +1,122 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import "../state" + +Rectangle { + id: root + + required property DummyPoolState poolState + readonly property string estimateHelp: qsTr("This value is an estimate from the current dummy reserves and your share of total LP supply.") + + color: "#1D1D1D" + implicitHeight: content.implicitHeight + 20 + radius: 8 + border.color: "#343434" + border.width: 1 + + ColumnLayout { + id: content + + anchors.fill: parent + anchors.margins: 10 + spacing: 4 + + Text { + color: "#E7E1D8" + font.bold: true + font.pixelSize: 16 + text: qsTr("Pool position") + + Layout.fillWidth: true + } + + Text { + color: "#F26A21" + font.pixelSize: 12 + text: qsTr("You have no position in this pool") + visible: root.poolState.userLpBalance === 0 + + Layout.fillWidth: true + } + + SummaryRow { + label: qsTr("Token A") + value: root.poolState.tokenA + + Layout.fillWidth: true + } + + SummaryRow { + label: qsTr("Token B") + value: root.poolState.tokenB + + Layout.fillWidth: true + } + + SummaryRow { + label: qsTr("Fee tier") + value: root.poolState.feeTier + + Layout.fillWidth: true + } + + SummaryRow { + label: qsTr("Your LP tokens") + value: root.poolState.formatInteger(root.poolState.userLpBalance) + visible: root.poolState.userLpBalance > 0 + + Layout.fillWidth: true + } + + SummaryRow { + estimated: true + estimateHelp: root.estimateHelp + label: qsTr("Pool share") + value: root.poolState.formatPoolShare(root.poolState.poolShare) + visible: root.poolState.userLpBalance > 0 + + Layout.fillWidth: true + } + + SummaryRow { + estimated: true + estimateHelp: root.estimateHelp + label: qsTr("Your Token A") + value: "\u2248 " + root.poolState.formatTokenAmount(root.poolState.userOwnedA, root.poolState.tokenA) + visible: root.poolState.userLpBalance > 0 + + Layout.fillWidth: true + } + + SummaryRow { + estimated: true + estimateHelp: root.estimateHelp + label: qsTr("Your Token B") + value: "\u2248 " + root.poolState.formatTokenAmount(root.poolState.userOwnedB, root.poolState.tokenB) + visible: root.poolState.userLpBalance > 0 + + Layout.fillWidth: true + } + + SummaryRow { + label: qsTr("Total reserve A") + value: root.poolState.formatTokenAmount(root.poolState.reserveA, root.poolState.tokenA) + + Layout.fillWidth: true + } + + SummaryRow { + label: qsTr("Total reserve B") + value: root.poolState.formatTokenAmount(root.poolState.reserveB, root.poolState.tokenB) + + Layout.fillWidth: true + } + + SummaryRow { + label: qsTr("Total LP supply") + value: root.poolState.formatInteger(root.poolState.totalLpSupply) + + Layout.fillWidth: true + } + } +} diff --git a/amm-ui/qml/components/SummaryRow.qml b/amm-ui/qml/components/SummaryRow.qml new file mode 100644 index 0000000..29f8999 --- /dev/null +++ b/amm-ui/qml/components/SummaryRow.qml @@ -0,0 +1,60 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +Item { + id: root + + property string label: "" + property string value: "" + property bool estimated: false + property string estimateHelp: qsTr("This value is derived from your LP token balance, total LP supply, and current pool reserves.") + + implicitHeight: Math.max(18, Math.max(labelText.implicitHeight, valueGroup.implicitHeight)) + + RowLayout { + anchors.fill: parent + spacing: 8 + + Text { + id: labelText + + color: "#A9A098" + elide: Text.ElideRight + font.pixelSize: 12 + text: root.label + verticalAlignment: Text.AlignVCenter + + Layout.fillWidth: true + } + + RowLayout { + id: valueGroup + + spacing: 4 + + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + + Text { + color: "#E7E1D8" + elide: Text.ElideRight + font.bold: true + font.pixelSize: 12 + horizontalAlignment: Text.AlignRight + text: root.value + verticalAlignment: Text.AlignVCenter + + Layout.maximumWidth: 178 + } + + EstimateInfoButton { + enabled: root.estimated + helpText: root.estimateHelp + opacity: root.estimated ? 1 : 0 + visible: root.estimated + + Layout.preferredHeight: 18 + Layout.preferredWidth: root.estimated ? 18 : 0 + } + } + } +} diff --git a/amm-ui/qml/state/DummyPoolState.qml b/amm-ui/qml/state/DummyPoolState.qml new file mode 100644 index 0000000..570e738 --- /dev/null +++ b/amm-ui/qml/state/DummyPoolState.qml @@ -0,0 +1,66 @@ +import QtQuick 2.15 + +QtObject { + id: root + + property string tokenA: "USDC" + property string tokenB: "ETH" + property string feeTier: "0.30%" + property real userLpBalance: 1118033 + property real reserveA: 1000000 + property real reserveB: 500 + property real totalLpSupply: 22360679 + + readonly property real poolShare: totalLpSupply > 0 ? userLpBalance / totalLpSupply : 0 + readonly property real userOwnedA: reserveA * poolShare + readonly property real userOwnedB: reserveB * poolShare + + function applyAddLiquidity(actualA, actualB, mintedLp) { + const safeA = Math.max(0, Number(actualA) || 0); + const safeB = Math.max(0, Number(actualB) || 0); + const safeLp = Math.max(0, Number(mintedLp) || 0); + + reserveA += safeA; + reserveB += safeB; + totalLpSupply += safeLp; + userLpBalance += safeLp; + } + + function applyRemoveLiquidity(withdrawA, withdrawB, burnedLp) { + const safeA = Math.max(0, Number(withdrawA) || 0); + const safeB = Math.max(0, Number(withdrawB) || 0); + const safeLp = Math.max(0, Number(burnedLp) || 0); + + reserveA = Math.max(0, reserveA - safeA); + reserveB = Math.max(0, reserveB - safeB); + totalLpSupply = Math.max(0, totalLpSupply - safeLp); + userLpBalance = Math.max(0, userLpBalance - safeLp); + } + + function resetDummyState() { + tokenA = "USDC"; + tokenB = "ETH"; + feeTier = "0.30%"; + userLpBalance = 1118033; + reserveA = 1000000; + reserveB = 500; + totalLpSupply = 22360679; + } + + function formatInteger(value) { + const rounded = Math.round(Number(value) || 0); + return rounded.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + } + + function formatTokenAmount(value, token) { + return formatInteger(value) + " " + token; + } + + function formatLpAmount(value) { + return formatInteger(value) + " LP"; + } + + function formatPoolShare(value) { + return "\u2248 " + (Math.max(0, Number(value) || 0) * 100).toFixed(2) + "%"; + } +}