2023-04-05 11:42:12 +00:00
|
|
|
import QtQuick 2.15
|
|
|
|
import QtQml 2.15
|
|
|
|
import QtQuick.Controls 2.15
|
|
|
|
import QtQuick.Layouts 1.15
|
2021-10-05 20:50:22 +00:00
|
|
|
|
2022-07-14 11:03:36 +00:00
|
|
|
import StatusQ.Core 0.1
|
2021-10-20 10:21:23 +00:00
|
|
|
import StatusQ.Components 0.1
|
|
|
|
import StatusQ.Controls 0.1
|
2022-09-01 15:34:27 +00:00
|
|
|
import StatusQ.Core.Theme 0.1
|
2023-06-13 08:18:53 +00:00
|
|
|
import StatusQ.Popups 0.1
|
2021-10-20 10:21:23 +00:00
|
|
|
|
2023-03-23 07:34:05 +00:00
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
|
2022-07-14 11:03:36 +00:00
|
|
|
import utils 1.0
|
|
|
|
|
2022-03-25 08:46:47 +00:00
|
|
|
import "../panels"
|
2021-10-05 20:50:22 +00:00
|
|
|
import "../popups"
|
|
|
|
import "../stores"
|
|
|
|
import "../controls"
|
|
|
|
|
2023-05-10 11:54:06 +00:00
|
|
|
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
2023-06-02 18:00:31 +00:00
|
|
|
import AppLayouts.Wallet.popups 1.0
|
|
|
|
import AppLayouts.Wallet.controls 1.0
|
2023-05-10 11:54:06 +00:00
|
|
|
|
2022-09-01 15:34:27 +00:00
|
|
|
ColumnLayout {
|
2023-01-12 23:26:48 +00:00
|
|
|
id: root
|
2021-10-05 20:50:22 +00:00
|
|
|
|
2023-04-25 16:54:50 +00:00
|
|
|
property var overview
|
2021-10-05 20:50:22 +00:00
|
|
|
property int pageSize: 20 // number of transactions per page
|
|
|
|
|
2022-09-05 09:15:47 +00:00
|
|
|
signal launchTransactionDetail(var transaction)
|
|
|
|
|
2021-10-05 20:50:22 +00:00
|
|
|
function fetchHistory() {
|
2023-04-25 16:54:50 +00:00
|
|
|
if (!RootStore.isFetchingHistory(root.overview.mixedcaseAddress)) {
|
2023-01-12 23:26:48 +00:00
|
|
|
d.isLoading = true
|
2023-04-25 16:54:50 +00:00
|
|
|
RootStore.loadTransactionsForAccount(root.overview.mixedcaseAddress, pageSize)
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-12 23:26:48 +00:00
|
|
|
QtObject {
|
|
|
|
id: d
|
|
|
|
property bool isLoading: false
|
|
|
|
}
|
|
|
|
|
2021-10-05 20:50:22 +00:00
|
|
|
Connections {
|
2021-10-21 08:22:05 +00:00
|
|
|
target: RootStore.history
|
2023-01-18 09:25:36 +00:00
|
|
|
function onLoadingTrxHistoryChanged(isLoading: bool, address: string) {
|
2023-04-25 16:54:50 +00:00
|
|
|
if (root.overview.mixedcaseAddress.toLowerCase() === address.toLowerCase()) {
|
2023-01-12 23:26:48 +00:00
|
|
|
d.isLoading = isLoading
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StyledText {
|
|
|
|
id: nonArchivalNodeError
|
2022-09-01 15:34:27 +00:00
|
|
|
Layout.alignment: Qt.AlignTop
|
|
|
|
|
2021-10-05 20:50:22 +00:00
|
|
|
visible: RootStore.isNonArchivalNode
|
2022-04-04 11:26:30 +00:00
|
|
|
text: qsTr("Status Desktop is connected to a non-archival node. Transaction history may be incomplete.")
|
2021-10-05 20:50:22 +00:00
|
|
|
font.pixelSize: Style.current.primaryTextFontSize
|
|
|
|
color: Style.current.danger
|
|
|
|
}
|
|
|
|
|
2023-05-02 08:01:04 +00:00
|
|
|
ShapeRectangle {
|
2021-10-05 20:50:22 +00:00
|
|
|
id: noTxs
|
2023-05-02 08:01:04 +00:00
|
|
|
Layout.fillWidth: true
|
|
|
|
Layout.preferredHeight: 42
|
2023-01-12 23:26:48 +00:00
|
|
|
visible: !d.isLoading && transactionListRoot.count === 0
|
2021-10-05 20:50:22 +00:00
|
|
|
font.pixelSize: Style.current.primaryTextFontSize
|
2023-05-02 08:01:04 +00:00
|
|
|
text: qsTr("Activity for this account will appear here")
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|
|
|
|
|
2023-06-02 18:00:31 +00:00
|
|
|
Rectangle {
|
|
|
|
id: filterComponent
|
|
|
|
visible: !d.isLoading && transactionListRoot.count !== 0
|
|
|
|
Layout.fillWidth: true
|
|
|
|
Layout.preferredHeight: 50
|
|
|
|
color: Theme.palette.transparent
|
|
|
|
StatusRoundButton {
|
|
|
|
anchors.left: parent.left
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
width: 32
|
|
|
|
height: 32
|
|
|
|
border.width: 1
|
|
|
|
border.color: Theme.palette.directColor8
|
|
|
|
icon.name: "filter"
|
|
|
|
onClicked: activityFilter.popup(x, y + height + 4)
|
|
|
|
type: StatusRoundButton.Type.Tertiary
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Separator {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
visible: filterComponent.visible
|
|
|
|
}
|
|
|
|
|
2022-07-14 11:03:36 +00:00
|
|
|
StatusListView {
|
2021-10-05 20:50:22 +00:00
|
|
|
id: transactionListRoot
|
2022-09-20 12:27:52 +00:00
|
|
|
objectName: "walletAccountTransactionList"
|
2022-09-01 15:34:27 +00:00
|
|
|
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
|
|
|
|
|
2023-03-23 07:34:05 +00:00
|
|
|
property string firstSection
|
|
|
|
|
|
|
|
model: SortFilterProxyModel {
|
|
|
|
id: txModel
|
|
|
|
|
|
|
|
sourceModel: RootStore.historyTransactions
|
|
|
|
|
|
|
|
// LocaleUtils is not accessable from inside expression, but local function works
|
|
|
|
property var formatDate: (ms) => LocaleUtils.formatDate(ms, Locale.ShortFormat)
|
|
|
|
sorters: RoleSorter {
|
|
|
|
roleName: "timestamp"
|
|
|
|
sortOrder: Qt.DescendingOrder
|
|
|
|
}
|
|
|
|
proxyRoles: ExpressionRole {
|
|
|
|
name: "date"
|
|
|
|
expression: {
|
2023-04-05 11:42:12 +00:00
|
|
|
return timestamp > 0 ? txModel.formatDate(timestamp * 1000) : (d.isLoading ? " " : "") // not empty, because section will not be displayed when loading if empty
|
2023-03-23 07:34:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-05 11:42:12 +00:00
|
|
|
delegate: transactionDelegate
|
2023-03-23 07:34:05 +00:00
|
|
|
|
2023-01-12 23:26:48 +00:00
|
|
|
footer: footerComp
|
2021-10-05 20:50:22 +00:00
|
|
|
|
2023-04-05 11:42:12 +00:00
|
|
|
displaced: Transition { // TODO Remove animation when ordered fetching of transactions is implemented
|
2023-03-23 07:34:05 +00:00
|
|
|
NumberAnimation { properties: "y"; duration: 250; easing.type: Easing.Linear; alwaysRunToEnd: true }
|
|
|
|
}
|
|
|
|
|
|
|
|
readonly property point lastVisibleItemPos: Qt.point(0, contentY + height - 1)
|
|
|
|
property int lastVisibleIndex: indexAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
|
|
|
|
|
|
|
|
onCountChanged: {
|
2023-04-05 11:42:12 +00:00
|
|
|
// Preserve last visible item in view when more items added at the end or
|
|
|
|
// inbetween
|
|
|
|
// TODO Remove this logic, when new activity design is implemented
|
|
|
|
// and items are loaded in order
|
2023-03-23 07:34:05 +00:00
|
|
|
const lastVisibleItem = itemAtIndex(lastVisibleIndex)
|
|
|
|
const newItem = itemAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
|
|
|
|
const lastVisibleItemY = lastVisibleItem ? lastVisibleItem.y : -1
|
|
|
|
if (newItem) {
|
|
|
|
if (newItem.y < lastVisibleItemY) { // item inserted higher than last visible
|
|
|
|
lastVisibleIndex = indexAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currentIndex: 0
|
|
|
|
|
|
|
|
property bool userScrolled: false
|
|
|
|
|
2023-04-05 11:42:12 +00:00
|
|
|
// TODO Remove this logic, when new activity design is implemented
|
|
|
|
// and items are loaded in order
|
2023-03-23 07:34:05 +00:00
|
|
|
onMovingVerticallyChanged: {
|
|
|
|
if (!userScrolled) {
|
|
|
|
userScrolled = true
|
|
|
|
currentIndex = Qt.binding(() => lastVisibleIndex >= 0 ? lastVisibleIndex : (count - 1))
|
|
|
|
}
|
|
|
|
|
|
|
|
lastVisibleIndex = indexAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
|
|
|
|
}
|
|
|
|
|
2022-09-01 15:34:27 +00:00
|
|
|
ScrollBar.vertical: StatusScrollBar {}
|
|
|
|
|
2023-03-23 07:34:05 +00:00
|
|
|
section.property: "date"
|
2023-05-09 08:05:03 +00:00
|
|
|
topMargin: -20 // Top margin for first section. Section cannot have different sizes
|
2023-05-04 04:43:39 +00:00
|
|
|
section.delegate: ColumnLayout {
|
2023-04-05 11:42:12 +00:00
|
|
|
id: sectionDelegate
|
|
|
|
|
|
|
|
readonly property bool isFirstSection: ListView.view.firstSection === section
|
|
|
|
|
2023-03-23 07:34:05 +00:00
|
|
|
width: ListView.view.width
|
2023-05-04 04:43:39 +00:00
|
|
|
// display always first section. Other sections when more items are being fetched must not be visible
|
2023-05-09 08:05:03 +00:00
|
|
|
// 1 because we don't use empty for loading state
|
|
|
|
// Additionaly height must be defined so all sections use same height to to glitch sections when updating model
|
|
|
|
height: isFirstSection || section.length > 1 ? 58 : 0
|
|
|
|
visible: height > 0 // display always first section. Other sections when more items are being fetched must not be visible
|
2023-05-04 04:43:39 +00:00
|
|
|
spacing: 0
|
2021-10-05 20:50:22 +00:00
|
|
|
|
2023-03-23 07:34:05 +00:00
|
|
|
required property string section
|
|
|
|
|
2023-05-04 04:43:39 +00:00
|
|
|
Separator {
|
|
|
|
Layout.fillWidth: true
|
2023-05-09 08:05:03 +00:00
|
|
|
Layout.topMargin: 20
|
2023-05-04 04:43:39 +00:00
|
|
|
implicitHeight: 1
|
2023-06-02 18:00:31 +00:00
|
|
|
visible: !sectionDelegate.isFirstSection
|
2023-05-04 04:43:39 +00:00
|
|
|
}
|
|
|
|
|
2023-04-05 11:42:12 +00:00
|
|
|
StatusTextWithLoadingState {
|
|
|
|
id: sectionText
|
2023-05-09 08:05:03 +00:00
|
|
|
Layout.alignment: Qt.AlignBottom
|
2023-05-04 04:43:39 +00:00
|
|
|
leftPadding: 16
|
|
|
|
bottomPadding: 8
|
2023-04-05 11:42:12 +00:00
|
|
|
text: loading ? "dummy" : parent.section // dummy text because loading component height depends on text height, and not visible with height == 0
|
|
|
|
Binding on width {
|
|
|
|
when: sectionText.loading
|
|
|
|
value: 56
|
|
|
|
restoreMode: Binding.RestoreBindingOrValue
|
|
|
|
}
|
|
|
|
customColor: Theme.palette.baseColor1
|
2023-05-04 04:43:39 +00:00
|
|
|
font.pixelSize: 13
|
|
|
|
verticalAlignment: Qt.AlignBottom
|
2023-04-05 11:42:12 +00:00
|
|
|
loading: { // First section must be loading when first item in the list is loading. Other sections are never visible until have date value
|
|
|
|
if (parent.ListView.view.count > 0) {
|
|
|
|
const firstItem = parent.ListView.view.itemAtIndex(0)
|
|
|
|
if (sectionDelegate.isFirstSection && firstItem && firstItem.loading) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2023-03-23 07:34:05 +00:00
|
|
|
}
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|
2023-03-06 15:52:20 +00:00
|
|
|
onAtYEndChanged: if(atYEnd && RootStore.historyTransactions.count > 0 && RootStore.historyTransactions.hasMore) fetchHistory()
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 08:18:53 +00:00
|
|
|
StatusMenu {
|
|
|
|
id: delegateMenu
|
|
|
|
|
|
|
|
hideDisabledItems: true
|
|
|
|
|
|
|
|
property var transaction
|
|
|
|
property var transactionDelegate
|
|
|
|
|
|
|
|
function openMenu(delegate, mouse) {
|
|
|
|
if (!delegate || !delegate.modelData)
|
|
|
|
return
|
|
|
|
|
|
|
|
delegateMenu.transactionDelegate = delegate
|
|
|
|
delegateMenu.transaction = delegate.modelData
|
|
|
|
repeatTransactionAction.enabled = !overview.isWatchOnlyAccount && delegate.transactionType === TransactionDelegate.Send
|
|
|
|
popup(delegate, mouse.x, mouse.y)
|
|
|
|
}
|
|
|
|
|
|
|
|
onClosed: {
|
|
|
|
delegateMenu.transaction = null
|
|
|
|
delegateMenu.transactionDelegate = null
|
|
|
|
}
|
|
|
|
|
|
|
|
StatusAction {
|
|
|
|
id: repeatTransactionAction
|
|
|
|
text: qsTr("Repeat transaction")
|
|
|
|
enabled: false
|
|
|
|
icon.name: "rotate"
|
|
|
|
onTriggered: {
|
|
|
|
if (!delegateMenu.transaction)
|
|
|
|
return
|
|
|
|
root.sendModal.open(delegateMenu.transaction.to)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
StatusSuccessAction {
|
|
|
|
text: qsTr("Copy details")
|
|
|
|
successText: qsTr("Details copied")
|
|
|
|
icon.name: "copy"
|
|
|
|
onTriggered: {
|
|
|
|
if (!delegateMenu.transactionDelegate)
|
|
|
|
return
|
|
|
|
RootStore.copyToClipboard(delegateMenu.transactionDelegate.getDetailsString())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
StatusMenuSeparator {
|
|
|
|
visible: filterAction.enabled
|
|
|
|
}
|
|
|
|
StatusAction {
|
|
|
|
id: filterAction
|
|
|
|
enabled: false
|
|
|
|
text: qsTr("Filter by similar")
|
|
|
|
icon.name: "filter"
|
|
|
|
onTriggered: {
|
|
|
|
// TODO apply filter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-01 15:34:27 +00:00
|
|
|
Component {
|
|
|
|
id: transactionDelegate
|
|
|
|
TransactionDelegate {
|
2023-04-05 11:42:12 +00:00
|
|
|
width: ListView.view.width
|
|
|
|
modelData: model
|
2023-06-12 13:04:39 +00:00
|
|
|
transactionType: isModelDataValid && modelData.to.toLowerCase() === root.overview.mixedcaseAddress.toLowerCase() ? Constants.TransactionType.Receive : Constants.TransactionType.Send
|
2023-02-17 12:56:31 +00:00
|
|
|
currentCurrency: RootStore.currentCurrency
|
2023-04-05 11:42:12 +00:00
|
|
|
cryptoValue: isModelDataValid ? modelData.value.amount : 0.0
|
|
|
|
fiatValue: isModelDataValid ? RootStore.getFiatValue(cryptoValue, symbol, currentCurrency): 0.0
|
|
|
|
networkIcon: isModelDataValid ? RootStore.getNetworkIcon(modelData.chainId) : ""
|
|
|
|
networkColor: isModelDataValid ? RootStore.getNetworkColor(modelData.chainId) : ""
|
2023-05-10 11:54:06 +00:00
|
|
|
networkName: isModelDataValid ? RootStore.getNetworkFullName(modelData.chainId) : ""
|
2023-04-05 11:42:12 +00:00
|
|
|
symbol: isModelDataValid && !!modelData.symbol ? modelData.symbol : ""
|
|
|
|
transferStatus: isModelDataValid ? RootStore.hex2Dec(modelData.txStatus) : ""
|
2023-05-10 11:54:06 +00:00
|
|
|
timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000) : ""
|
|
|
|
addressNameTo: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.to) : ""
|
|
|
|
addressNameFrom: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.from) : ""
|
2023-06-13 08:18:53 +00:00
|
|
|
rootStore: RootStore
|
|
|
|
walletRootStore: WalletStores.RootStore
|
|
|
|
onClicked: {
|
|
|
|
if (mouse.button === Qt.RightButton) {
|
|
|
|
delegateMenu.openMenu(this, mouse, modelData)
|
|
|
|
} else {
|
|
|
|
launchTransactionDetail(modelData)
|
|
|
|
}
|
|
|
|
}
|
2023-04-05 11:42:12 +00:00
|
|
|
loading: isModelDataValid ? modelData.loadingTransaction : false
|
|
|
|
|
|
|
|
Component.onCompleted: {
|
|
|
|
if (index == 0)
|
|
|
|
ListView.view.firstSection = date
|
|
|
|
}
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-01 15:34:27 +00:00
|
|
|
Component {
|
2023-01-12 23:26:48 +00:00
|
|
|
id: footerComp
|
|
|
|
ColumnLayout {
|
|
|
|
width: root.width
|
|
|
|
visible: !RootStore.historyTransactions.hasMore && transactionListRoot.count !== 0
|
|
|
|
spacing: 12
|
2023-05-31 07:47:52 +00:00
|
|
|
Separator {
|
|
|
|
Layout.topMargin: Style.current.bigPadding
|
|
|
|
Layout.fillWidth: true
|
2023-01-12 23:26:48 +00:00
|
|
|
visible: !RootStore.historyTransactions.hasMore
|
|
|
|
}
|
|
|
|
StatusBaseText {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
|
|
text: qsTr("You have reached the beginning of the activity for this account")
|
|
|
|
font.pixelSize: 13
|
|
|
|
color: Theme.palette.baseColor1
|
|
|
|
visible: !RootStore.historyTransactions.hasMore
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
}
|
|
|
|
StatusButton {
|
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
|
|
text: qsTr("Back to most recent transaction")
|
2023-05-31 07:47:52 +00:00
|
|
|
visible: !RootStore.historyTransactions.hasMore && transactionListRoot.contentHeight > transactionListRoot.height
|
2023-01-12 23:26:48 +00:00
|
|
|
onClicked: transactionListRoot.positionViewAtBeginning()
|
|
|
|
}
|
|
|
|
}
|
2022-09-01 15:34:27 +00:00
|
|
|
}
|
2023-06-02 18:00:31 +00:00
|
|
|
|
2023-06-13 11:00:05 +00:00
|
|
|
// To-do connect with backend once its implemented
|
2023-06-02 18:00:31 +00:00
|
|
|
ActivityFilterMenu {
|
|
|
|
id: activityFilter
|
2023-06-13 11:00:05 +00:00
|
|
|
selectedTime: Constants.TransactionTimePeriod.All
|
2023-06-02 18:00:31 +00:00
|
|
|
onSetSelectedTime: {
|
|
|
|
// To do connect with n=backend to set time range
|
2023-06-13 11:00:05 +00:00
|
|
|
if(selectedTime === Constants.TransactionTimePeriod.Custom) {
|
2023-06-02 18:00:31 +00:00
|
|
|
customDateRangePicker.open()
|
|
|
|
}
|
|
|
|
}
|
2023-06-13 11:00:05 +00:00
|
|
|
typeFilters: [
|
|
|
|
Constants.TransactionType.Send,
|
|
|
|
Constants.TransactionType.Receive,
|
|
|
|
Constants.TransactionType.Buy,
|
|
|
|
Constants.TransactionType.Swap,
|
|
|
|
Constants.TransactionType.Bridge
|
|
|
|
]
|
|
|
|
statusFilters: [
|
|
|
|
Constants.TransactionStatus.Failed,
|
|
|
|
Constants.TransactionStatus.Pending,
|
|
|
|
Constants.TransactionStatus.Complete,
|
|
|
|
Constants.TransactionStatus.Finished
|
|
|
|
]
|
|
|
|
store: RootStore
|
2023-06-02 18:00:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// To-do update once https://github.com/status-im/status-desktop/pull/10916 is updated and connect with backend values
|
|
|
|
StatusDateRangePicker {
|
|
|
|
id: customDateRangePicker
|
|
|
|
destroyOnClose: false
|
|
|
|
fromTimestamp: new Date().setDate(new Date().getDate() - 7)
|
|
|
|
// onNewRangeSet: d.setCustomTimeRange(fromTimestamp, toTimestamp)
|
|
|
|
}
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|