feat(savedaddresses): saved address details popup implementation

Within this commit a new popup is introduced with the requested UI effect.

Closes #13096
This commit is contained in:
Sale Djenic 2024-02-19 09:40:16 +01:00 committed by saledjenic
parent 12c24c4f15
commit 34f801231c
8 changed files with 444 additions and 64 deletions

View File

@ -29,8 +29,19 @@ StatusListItem {
property bool areTestNetworksEnabled: false
property bool isSepoliaEnabled: false
property int usage: SavedAddressesDelegate.Usage.Delegate
property bool showButtons: sensor.containsMouse
property alias sendButton: sendButton
signal aboutToOpenPopup()
signal openSendModal(string recipient)
enum Usage {
Delegate,
Item
}
implicitWidth: ListView.view ? ListView.view.width : 0
title: name
@ -65,7 +76,6 @@ StatusListItem {
statusListItemIcon.hoverEnabled: true
statusListItemComponentsSlot.spacing: 0
property bool showButtons: sensor.containsMouse
QtObject {
id: d
@ -74,8 +84,22 @@ StatusListItem {
readonly property var preferredSharedNetworkNamesArray: root.chainShortNames.split(":").filter(Boolean)
}
onClicked: {
if (root.usage === SavedAddressesDelegate.Usage.Item) {
return
}
Global.openSavedAddressActivityPopup({
name: root.name,
address: root.address,
chainShortNames: root.chainShortNames,
ens: root.ens,
colorId: root.colorId
})
}
components: [
StatusRoundButton {
id: sendButton
visible: !!root.name && root.showButtons
type: StatusRoundButton.Type.Quinary
radius: 8
@ -138,6 +162,9 @@ StatusListItem {
objectName: "editSavedAddress"
assetSettings.name: "pencil-outline"
onTriggered: {
if (root.usage === SavedAddressesDelegate.Usage.Item) {
root.aboutToOpenPopup()
}
Global.openAddEditSavedAddressesPopup({
edit: true,
address: menu.address,
@ -167,6 +194,9 @@ StatusListItem {
objectName: "showQrSavedAddressAction"
assetSettings.name: "qr"
onTriggered: {
if (root.usage === SavedAddressesDelegate.Usage.Item) {
root.aboutToOpenPopup()
}
Global.openShowQRPopup({
showSingleAccount: true,
showForSavedAddress: true,
@ -186,6 +216,9 @@ StatusListItem {
objectName: "viewActivitySavedAddressAction"
assetSettings.name: "wallet"
onTriggered: {
if (root.usage === SavedAddressesDelegate.Usage.Item) {
root.aboutToOpenPopup()
}
Global.changeAppSectionBySectionType(Constants.appSection.wallet,
WalletLayout.LeftPanelSelection.AllAddresses,
WalletLayout.RightPanelSelection.Activity,
@ -235,6 +268,9 @@ StatusListItem {
assetSettings.name: "delete"
objectName: "deleteSavedAddress"
onTriggered: {
if (root.usage === SavedAddressesDelegate.Usage.Item) {
root.aboutToOpenPopup()
}
Global.openDeleteSavedAddressesPopup({
name: menu.name,
address: menu.address,

View File

@ -203,11 +203,7 @@ Column {
delegate: ActivityFilterTagItem {
tagPrimaryLabel.text: {
let savedAddress = root.store.getSavedAddress(modelData)
if (!!savedAddress.ens) {
return savedAddress.ens
}
return savedAddress.chainShortNames + StatusQUtils.Utils.elideText(modelData,6,4)
return savedAddress.name
}
onClosed: activityFilterStore.toggleSavedAddress(modelData)
}

View File

@ -0,0 +1,275 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import AppLayouts.stores 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStore
import utils 1.0
import shared.views 1.0
import "../controls"
import ".."
StatusModal {
id: root
property var networkConnectionStore
property var contactsStore
property var sendModalPopup
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
hasCloseButton: false
width: d.popupWidth
contentHeight: content.height
onClosed: {
root.close()
walletSection.activityController.setFilterToAddresses(JSON.stringify([]))
walletSection.activityController.updateFilter()
}
function initWithParams(params = {}) {
d.name = params.name?? ""
d.address = params.address?? Constants.zeroAddress
d.ens = params.ens?? ""
d.colorId = params.colorId?? ""
d.chainShortNames = params.chainShortNames?? ""
walletSection.activityController.setFilterToAddresses(JSON.stringify([d.address]))
walletSection.activityController.updateFilter()
}
QtObject {
id: d
readonly property int popupWidth: 477
readonly property int popupHeight: 672
readonly property int contentWidth: d.popupWidth - 2 * d.margin
readonly property int margin: 24
readonly property int radius: 8
property string name: ""
property string address: Constants.zeroAddress
property string ens: ""
property string colorId: ""
property string chainShortNames: ""
readonly property string visibleAddress: !!d.ens? d.ens : d.address
readonly property int yRange: historyView.firstItemOffset
readonly property real extendedViewOpacity: {
if (historyView.yPosition <= 0) {
return 1
}
let op = 1 - historyView.yPosition / d.yRange
if (op > 0) {
return op
}
return 0
}
readonly property bool showSplitLine: d.extendedViewOpacity === 0
}
component Spacer: Item {
width: 1
}
showFooter: false
showHeader: false
Rectangle {
id: content
width: d.popupWidth
height: d.popupHeight
color: Theme.palette.statusModal.backgroundColor
radius: d.radius
Item {
id: fixedHeader
anchors.top: parent.top
anchors.left: parent.left
implicitWidth: parent.width
implicitHeight: childrenRect.height
Column {
anchors.top: parent.top
width: parent.width
Spacer {
height: 24
}
SavedAddressesDelegate {
id: savedAddress
implicitHeight: 72
implicitWidth: d.contentWidth
anchors.horizontalCenter: parent.horizontalCenter
leftPadding: 0
border.color: "transparent"
usage: SavedAddressesDelegate.Usage.Item
showButtons: true
statusListItemComponentsSlot.spacing: 4
statusListItemSubTitle.visible: d.extendedViewOpacity !== 1
statusListItemSubTitle.opacity: 1 - d.extendedViewOpacity
statusListItemSubTitle.customColor: Theme.palette.directColor1
statusListItemSubTitle.text: {
if (statusListItemSubTitle.visible) {
if (!!d.ens) {
return d.ens
}
else {
return WalletUtils.colorizedChainPrefix(d.chainShortNames) + Utils.richColorText(StatusQUtils.Utils.elideText(d.address,6,4), Theme.palette.directColor1)
}
}
return ""
}
sendButton.visible: d.extendedViewOpacity !== 1
sendButton.opacity: 1 - d.extendedViewOpacity
sendButton.type: StatusRoundButton.Type.Primary
asset.width: 72
asset.height: 72
asset.letterSize: 32
bgColor: Theme.palette.statusListItem.backgroundColor
store: WalletStore.RootStore
contactsStore: root.contactsStore
networkConnectionStore: root.networkConnectionStore
name: d.name
address: d.address
chainShortNames: d.chainShortNames
ens: d.ens
colorId: d.colorId
statusListItemTitle.font.pixelSize: 22
statusListItemTitle.font.bold: Font.Bold
areTestNetworksEnabled: WalletStore.RootStore.areTestNetworksEnabled
isSepoliaEnabled: WalletStore.RootStore.isSepoliaEnabled
onAboutToOpenPopup: {
root.close()
}
onOpenSendModal: {
root.close()
root.sendModalPopup.open(recipient)
}
}
Spacer {
height: 20
}
Rectangle {
width: parent.width
height: 4
opacity: 0.5
color: d.showSplitLine? Style.current.separator : "transparent"
}
}
}
Item {
id: extendedView
anchors.top: fixedHeader.bottom
anchors.left: parent.left
implicitWidth: parent.width
implicitHeight: childrenRect.height
z: d.extendedViewOpacity === 1? 1 : 0
Column {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
width: d.contentWidth
Rectangle {
opacity: d.extendedViewOpacity
width: parent.width
height: Math.max(addressText.height, copyButton.height) + 24
color: "transparent"
radius: d.radius
border.color: Theme.palette.baseColor5
border.width: 1
StatusBaseText {
id: addressText
anchors.left: parent.left
anchors.right: copyButton.left
anchors.rightMargin: Style.current.padding
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
text: !!d.ens? d.ens : WalletUtils.colorizedChainPrefix(d.chainShortNames) + d.address
wrapMode: Text.WrapAnywhere
font.pixelSize: 15
color: Theme.palette.directColor1
}
StatusRoundButton {
id: copyButton
width: 24
height: 24
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.top: addressText.top
icon.name: "copy"
type: StatusRoundButton.Type.Tertiary
onClicked: WalletStore.RootStore.copyToClipboard(d.visibleAddress)
}
}
Spacer {
height: 16
}
StatusButton {
opacity: d.extendedViewOpacity
width: parent.width
radius: d.radius
text: qsTr("Send")
icon.name: "send"
enabled: root.networkConnectionStore.sendBuyBridgeEnabled
onClicked: {
root.close()
root.sendModalPopup.open(d.visibleAddress)
}
}
Spacer {
height: 32
}
}
}
HistoryView {
id: historyView
anchors.top: fixedHeader.bottom
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.leftMargin: d.margin
width: parent.width - 2 * d.margin
disableShadowOnScroll: true
hideVerticalScrollbar: true
displayValues: false
firstItemOffset: extendedView.height
overview: ({
isWatchOnlyAccount: false,
mixedcaseAddress: d.address
})
}
}
}

View File

@ -5,3 +5,4 @@ ActivityTypeFilterSubMenu 1.0 filterSubMenus/ActivityTypeFilterSubMenu.qml
ReceiveModal 1.0 ReceiveModal.qml
AddEditSavedAddressPopup 1.0 AddEditSavedAddressPopup.qml
RemoveSavedAddressPopup 1.0 RemoveSavedAddressPopup.qml
SavedAddressActivityPopup 1.0 SavedAddressActivityPopup.qml

View File

@ -374,6 +374,10 @@ Item {
function onOpenShowQRPopup(params) {
showQR.open(params)
}
function onOpenSavedAddressActivityPopup(params) {
savedAddressActivity.open(params)
}
}
Connections {
@ -1953,6 +1957,39 @@ Item {
}
}
Loader {
id: savedAddressActivity
active: false
property var params
function open(params = {}) {
savedAddressActivity.params = params
savedAddressActivity.active = true
}
function close() {
savedAddressActivity.active = false
}
onLoaded: {
savedAddressActivity.item.initWithParams(savedAddressActivity.params)
savedAddressActivity.item.open()
}
sourceComponent: WalletPopups.SavedAddressActivityPopup {
networkConnectionStore: appMain.networkConnectionStore
contactsStore: appMain.rootStore.contactStore
sendModalPopup: sendModal
onClosed: {
savedAddressActivity.close()
}
}
}
DropAreaPanel {
id: rootDropAreaPanel

View File

@ -43,6 +43,7 @@ StatusListItem {
property var modelData
property string timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000) : ""
property bool showAllAccounts: false
property bool displayValues: true
required property var rootStore
required property var walletRootStore
@ -527,7 +528,6 @@ StatusListItem {
rightPadding: 16
enabled: !loading
loading: !isModelDataValid
color: sensor.containsMouse ? Theme.palette.baseColor5 : Style.current.transparent
statusListItemIcon.active: (loading || root.asset.name)
asset {
@ -681,7 +681,7 @@ StatusListItem {
// Right side components
components: [
Loader {
active: !headerStatusLoader.active
active: root.displayValues && !headerStatusLoader.active
visible: active
sourceComponent: ColumnLayout {
StatusTextWithLoadingState {

View File

@ -29,11 +29,17 @@ ColumnLayout {
property var overview
property bool showAllAccounts: false
property bool displayValues: true
property var sendModal
property bool filterVisible
property bool disableShadowOnScroll: false
property bool hideVerticalScrollbar: false
property int firstItemOffset: 0
property var selectedTransaction
property real yPosition: transactionListRoot.visibleArea.yPosition * transactionListRoot.contentHeight
signal launchTransactionDetail(int entryIndex)
function resetView() {
@ -43,7 +49,6 @@ ColumnLayout {
}
Component.onCompleted: {
filterPanelLoader.active = true
if (RootStore.transactionActivityStatus.isFilterDirty) {
WalletStores.RootStore.currentActivityFiltersStore.applyAllFilters()
}
@ -69,7 +74,8 @@ ColumnLayout {
readonly property bool isInitialLoading: RootStore.loadingHistoryTransactions && transactionListRoot.count === 0
readonly property int loadingSectionWidth: 56
readonly property int topSectionMargin: 20
property bool firstSectionHeaderLoaded: false
property double lastRefreshTime
readonly property int maxSecondsBetweenRefresh: 3
@ -82,18 +88,21 @@ ColumnLayout {
StyledText {
id: nonArchivalNodeError
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
Layout.topMargin: root.firstItemOffset
visible: RootStore.isNonArchivalNode
text: qsTr("Status Desktop is connected to a non-archival node. Transaction history may be incomplete.")
font.pixelSize: Style.current.primaryTextFontSize
color: Style.current.danger
wrapMode: Text.WordWrap
}
ShapeRectangle {
id: noTxs
Layout.fillWidth: true
Layout.preferredHeight: 42
Layout.topMargin: !nonArchivalNodeError.visible? root.firstItemOffset : 0
visible: !d.isInitialLoading && !WalletStores.RootStore.currentActivityFiltersStore.filtersSet && transactionListRoot.count === 0
font.pixelSize: Style.current.primaryTextFontSize
text: qsTr("Activity for this account will appear here")
@ -114,18 +123,18 @@ ColumnLayout {
}
Item {
id: transactionListWrapper
Layout.alignment: Qt.AlignTop
Layout.topMargin: nonArchivalNodeError.visible || noTxs.visible ? Style.current.padding : 0
Layout.bottomMargin: Style.current.padding
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle { // Shadow behind delegates when scrolling
anchors.top: topListSeparator.bottom
anchors.top: parent.top
width: parent.width
height: 4
color: Style.current.separator
visible: topListSeparator.visible
visible: !root.disableShadowOnScroll && !transactionListRoot.atYBeginning
}
StatusListView {
@ -177,42 +186,18 @@ ColumnLayout {
}
}
delegate: transactionDelegate
delegate: transactionDelegateComponent
headerPositioning: ListView.OverlayHeader
footer: footerComp
ScrollBar.vertical: StatusScrollBar {}
section.property: "date"
topMargin: d.isInitialLoading ? 0 : -d.topSectionMargin // Top margin for first section. Section cannot have different sizes
section.delegate: ColumnLayout {
id: sectionDelegate
width: ListView.view.width
height: 58
spacing: 0
required property string section
Separator {
Layout.fillWidth: true
Layout.topMargin: d.topSectionMargin
implicitHeight: 1
}
StatusBaseText {
id: sectionText
Layout.alignment: Qt.AlignBottom
leftPadding: Style.current.padding
bottomPadding: Style.current.halfPadding
text: parent.section
font.pixelSize: 13
verticalAlignment: Qt.AlignBottom
}
ScrollBar.vertical: StatusScrollBar {
policy: root.hideVerticalScrollbar? ScrollBar.AlwaysOff : ScrollBar.AsNeeded
}
visibleArea.onYPositionChanged: tryFetchMoreTransactions()
visibleArea.onYPositionChanged: {
tryFetchMoreTransactions()
}
Connections {
target: RootStore
@ -262,13 +247,6 @@ ColumnLayout {
size: StatusBaseButton.Size.Tiny
}
Separator {
id: topListSeparator
width: parent.width
visible: !transactionListRoot.atYBeginning
}
Connections {
target: RootStore
@ -418,24 +396,75 @@ ColumnLayout {
}
Component {
id: transactionDelegate
TransactionDelegate {
id: transactionDelegateComponent
ColumnLayout {
id: transactionDelegate
required property var model
required property int index
readonly property bool displaySectionHeader: index === 0 || model.date !== txModel.get(index - 1).date
readonly property bool displaySectionFooter: index === txModel.count-1 || model.date !== txModel.get(index + 1).date
width: ListView.view.width
modelData: model.activityEntry
timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000, true) : ""
rootStore: RootStore
walletRootStore: WalletStores.RootStore
showAllAccounts: root.showAllAccounts
onClicked: {
if (mouse.button === Qt.RightButton) {
delegateMenu.openMenu(this, mouse, modelData)
} else {
root.selectedTransaction = Qt.binding(() => model.activityEntry)
launchTransactionDetail(index)
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: root.firstItemOffset
visible: transactionDelegate.index === 0 && root.firstItemOffset > 0
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
visible: transactionDelegate.displaySectionHeader
color: Theme.palette.statusModal.backgroundColor
ColumnLayout {
anchors.left: parent.left
anchors.top: parent.top
width: parent.width
spacing: Style.current.halfPadding
Separator {
Layout.fillWidth: true
implicitHeight: 1
}
StatusBaseText {
leftPadding: Style.current.padding
bottomPadding: Style.current.halfPadding
text: transactionDelegate.model.date
font.pixelSize: 13
}
}
}
TransactionDelegate {
Layout.fillWidth: true
modelData: transactionDelegate.model.activityEntry
timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000, true) : ""
rootStore: RootStore
walletRootStore: WalletStores.RootStore
showAllAccounts: root.showAllAccounts
displayValues: root.displayValues
onClicked: {
if (mouse.button === Qt.RightButton) {
delegateMenu.openMenu(this, mouse, modelData)
} else {
root.selectedTransaction = Qt.binding(() => transactionDelegate.model.activityEntry)
launchTransactionDetail(transactionDelegate.index)
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 20
visible: transactionDelegate.displaySectionFooter
color: Theme.palette.statusModal.backgroundColor
}
}
}
@ -505,6 +534,11 @@ ColumnLayout {
visible: footerColumn.allActivityLoaded && transactionListRoot.contentHeight > transactionListRoot.height
onClicked: transactionListRoot.positionViewAtBeginning()
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.current.halfPadding
}
}
}
}

View File

@ -97,6 +97,7 @@ QtObject {
signal openAddEditSavedAddressesPopup(var params)
signal openDeleteSavedAddressesPopup(var params)
signal openShowQRPopup(var params)
signal openSavedAddressActivityPopup(var params)
function openProfilePopup(publicKey, parentPopup, cb) {
root.openProfilePopupRequested(publicKey, parentPopup, cb)