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

View File

@ -203,11 +203,7 @@ Column {
delegate: ActivityFilterTagItem { delegate: ActivityFilterTagItem {
tagPrimaryLabel.text: { tagPrimaryLabel.text: {
let savedAddress = root.store.getSavedAddress(modelData) let savedAddress = root.store.getSavedAddress(modelData)
if (!!savedAddress.ens) { return savedAddress.name
return savedAddress.ens
}
return savedAddress.chainShortNames + StatusQUtils.Utils.elideText(modelData,6,4)
} }
onClosed: activityFilterStore.toggleSavedAddress(modelData) 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 ReceiveModal 1.0 ReceiveModal.qml
AddEditSavedAddressPopup 1.0 AddEditSavedAddressPopup.qml AddEditSavedAddressPopup 1.0 AddEditSavedAddressPopup.qml
RemoveSavedAddressPopup 1.0 RemoveSavedAddressPopup.qml RemoveSavedAddressPopup 1.0 RemoveSavedAddressPopup.qml
SavedAddressActivityPopup 1.0 SavedAddressActivityPopup.qml

View File

@ -374,6 +374,10 @@ Item {
function onOpenShowQRPopup(params) { function onOpenShowQRPopup(params) {
showQR.open(params) showQR.open(params)
} }
function onOpenSavedAddressActivityPopup(params) {
savedAddressActivity.open(params)
}
} }
Connections { 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 { DropAreaPanel {
id: rootDropAreaPanel id: rootDropAreaPanel

View File

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

View File

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

View File

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