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-06-13 14:55:26 +00:00
|
|
|
import AppLayouts.Wallet.panels 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
|
|
|
|
2022-09-05 09:15:47 +00:00
|
|
|
signal launchTransactionDetail(var transaction)
|
|
|
|
|
2023-07-07 10:00:19 +00:00
|
|
|
onVisibleChanged: {
|
|
|
|
if (visible) {
|
|
|
|
RootStore.updateTransactionFilter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
target: RootStore
|
|
|
|
enabled: root.visible
|
|
|
|
function onIsTransactionFilterDirtyChanged() {
|
|
|
|
RootStore.updateTransactionFilter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-12 23:26:48 +00:00
|
|
|
QtObject {
|
|
|
|
id: d
|
2023-06-30 13:50:06 +00:00
|
|
|
readonly property bool isInitialLoading: RootStore.loadingHistoryTransactions && transactionListRoot.count === 0
|
2023-06-15 17:43:08 +00:00
|
|
|
property var activityFiltersStore: WalletStores.ActivityFiltersStore{}
|
2023-06-30 13:50:06 +00:00
|
|
|
readonly property int loadingSectionWidth: 56
|
2023-07-04 22:29:34 +00:00
|
|
|
readonly property int topSectionMargin: 20
|
2023-07-07 14:25:28 +00:00
|
|
|
|
|
|
|
property bool showRefreshButton: false
|
|
|
|
property double lastRefreshTime
|
|
|
|
readonly property int maxSecondsBetweenRefresh: 3
|
|
|
|
function refreshData() {
|
|
|
|
RootStore.resetFilter()
|
|
|
|
d.lastRefreshTime = Date.now()
|
|
|
|
d.showRefreshButton = false
|
|
|
|
}
|
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-06-30 13:50:06 +00:00
|
|
|
visible: !d.isInitialLoading && !d.activityFiltersStore.filtersSet && 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-13 14:55:26 +00:00
|
|
|
ActivityFilterPanel {
|
2023-06-02 18:00:31 +00:00
|
|
|
id: filterComponent
|
2023-06-30 13:50:06 +00:00
|
|
|
visible: !noTxs.visible && (!d.isInitialLoading || d.activityFiltersStore.filtersSet)
|
2023-06-02 18:00:31 +00:00
|
|
|
Layout.fillWidth: true
|
2023-06-15 17:43:08 +00:00
|
|
|
activityFilterStore: d.activityFiltersStore
|
|
|
|
store: WalletStores.RootStore
|
2023-06-30 13:50:06 +00:00
|
|
|
isLoading: d.isInitialLoading
|
2023-06-02 18:00:31 +00:00
|
|
|
}
|
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
Item {
|
2022-09-01 15:34:27 +00:00
|
|
|
Layout.alignment: Qt.AlignTop
|
|
|
|
Layout.topMargin: nonArchivalNodeError.visible || noTxs.visible ? Style.current.padding : 0
|
2023-06-16 09:37:17 +00:00
|
|
|
Layout.bottomMargin: Style.current.padding
|
2022-09-01 15:34:27 +00:00
|
|
|
Layout.fillWidth: true
|
|
|
|
Layout.fillHeight: true
|
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
Rectangle { // Shadow behind delegates when scrolling
|
|
|
|
anchors.top: topListSeparator.bottom
|
|
|
|
width: parent.width
|
|
|
|
height: 4
|
|
|
|
color: Style.current.separator
|
|
|
|
visible: topListSeparator.visible
|
|
|
|
}
|
2023-03-23 07:34:05 +00:00
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
StatusListView {
|
|
|
|
id: transactionListRoot
|
|
|
|
objectName: "walletAccountTransactionList"
|
|
|
|
anchors.fill: parent
|
2023-03-23 07:34:05 +00:00
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
model: SortFilterProxyModel {
|
|
|
|
id: txModel
|
|
|
|
|
|
|
|
sourceModel: RootStore.historyTransactions
|
|
|
|
|
|
|
|
// LocaleUtils is not accessable from inside expression, but local function works
|
2023-06-30 12:55:59 +00:00
|
|
|
property var daysTo: (d1, d2) => LocaleUtils.daysTo(d1, d2)
|
|
|
|
property var daysBetween: (d1, d2) => LocaleUtils.daysBetween(d1, d2)
|
|
|
|
property var getFirstDayOfTheCurrentWeek: () => LocaleUtils.getFirstDayOfTheCurrentWeek()
|
2023-06-16 09:37:17 +00:00
|
|
|
proxyRoles: ExpressionRole {
|
|
|
|
name: "date"
|
|
|
|
expression: {
|
2023-06-30 12:55:59 +00:00
|
|
|
if (model.activityEntry.timestamp === 0)
|
|
|
|
return ""
|
|
|
|
const currDate = new Date()
|
|
|
|
const timestampDate = new Date(model.activityEntry.timestamp * 1000)
|
|
|
|
const daysDiff = txModel.daysBetween(currDate, timestampDate)
|
|
|
|
const daysToBeginingOfThisWeek = txModel.daysTo(timestampDate, txModel.getFirstDayOfTheCurrentWeek())
|
|
|
|
|
|
|
|
if (daysDiff < 1) {
|
|
|
|
return qsTr("Today")
|
|
|
|
} else if (daysDiff < 2) {
|
|
|
|
return qsTr("Yesterday")
|
|
|
|
} else if (daysToBeginingOfThisWeek >= 0) {
|
|
|
|
return qsTr("Earlier this week")
|
|
|
|
} else if (daysToBeginingOfThisWeek > -7) {
|
|
|
|
return qsTr("Last week")
|
|
|
|
} else if (currDate.getMonth() === timestampDate.getMonth()) {
|
|
|
|
return qsTr("Earlier this month")
|
|
|
|
} else if ((new Date(new Date().setDate(0))).getMonth() === timestampDate.getMonth()) {
|
|
|
|
return qsTr("Last month")
|
|
|
|
}
|
|
|
|
return timestampDate.toLocaleDateString(Qt.locale(), "MMM yyyy")
|
2023-06-16 09:37:17 +00:00
|
|
|
}
|
2023-03-23 07:34:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
delegate: transactionDelegate
|
2023-03-23 07:34:05 +00:00
|
|
|
|
2023-07-04 22:29:34 +00:00
|
|
|
headerPositioning: ListView.OverlayHeader
|
|
|
|
header: headerComp
|
2023-06-16 09:37:17 +00:00
|
|
|
footer: footerComp
|
2021-10-05 20:50:22 +00:00
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
ScrollBar.vertical: StatusScrollBar {}
|
2022-09-01 15:34:27 +00:00
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
section.property: "date"
|
2023-07-04 22:29:34 +00:00
|
|
|
// Adding some magic number to align the top headerComp with the top of the list.
|
|
|
|
// TODO: have to be fixed properly and match the design
|
|
|
|
topMargin: d.isInitialLoading ? 0 : -(2 * d.topSectionMargin + 9) // Top margin for first section. Section cannot have different sizes
|
2023-06-16 09:37:17 +00:00
|
|
|
section.delegate: ColumnLayout {
|
|
|
|
id: sectionDelegate
|
2023-04-05 11:42:12 +00:00
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
width: ListView.view.width
|
2023-06-30 13:50:06 +00:00
|
|
|
height: 58
|
2023-06-16 09:37:17 +00:00
|
|
|
spacing: 0
|
2021-10-05 20:50:22 +00:00
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
required property string section
|
2023-03-23 07:34:05 +00:00
|
|
|
|
2023-06-16 09:37:17 +00:00
|
|
|
Separator {
|
|
|
|
Layout.fillWidth: true
|
2023-07-04 22:29:34 +00:00
|
|
|
Layout.topMargin: d.topSectionMargin
|
2023-06-16 09:37:17 +00:00
|
|
|
implicitHeight: 1
|
2023-04-05 11:42:12 +00:00
|
|
|
}
|
2023-06-16 09:37:17 +00:00
|
|
|
|
2023-06-30 13:50:06 +00:00
|
|
|
StatusBaseText {
|
2023-06-16 09:37:17 +00:00
|
|
|
id: sectionText
|
|
|
|
Layout.alignment: Qt.AlignBottom
|
2023-06-30 13:50:06 +00:00
|
|
|
leftPadding: Style.current.padding
|
|
|
|
bottomPadding: Style.current.halfPadding
|
|
|
|
text: parent.section
|
2023-06-16 09:37:17 +00:00
|
|
|
font.pixelSize: 13
|
|
|
|
verticalAlignment: Qt.AlignBottom
|
2023-04-05 11:42:12 +00:00
|
|
|
}
|
2023-03-23 07:34:05 +00:00
|
|
|
}
|
2023-06-30 13:50:06 +00:00
|
|
|
|
|
|
|
visibleArea.onYPositionChanged: tryFetchMoreTransactions()
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
target: RootStore
|
|
|
|
function onLoadingHistoryTransactionsChanged() {
|
|
|
|
// Calling timer instead directly to not cause binding loop
|
|
|
|
if (!RootStore.loadingHistoryTransactions)
|
|
|
|
fetchMoreTimer.start()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function tryFetchMoreTransactions() {
|
|
|
|
if (!footerItem || !RootStore.historyTransactions.hasMore)
|
|
|
|
return
|
|
|
|
const footerYPosition = footerItem.height / contentHeight
|
|
|
|
if (footerYPosition >= 1.0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// On startup, first loaded ListView will have heightRatio equal 0
|
|
|
|
if (footerYPosition + visibleArea.yPosition + visibleArea.heightRatio > 1.0) {
|
|
|
|
RootStore.fetchMoreTransactions()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Timer {
|
|
|
|
id: fetchMoreTimer
|
|
|
|
interval: 1
|
|
|
|
onTriggered: transactionListRoot.tryFetchMoreTransactions()
|
|
|
|
}
|
2023-06-16 09:37:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Separator {
|
|
|
|
id: topListSeparator
|
|
|
|
width: parent.width
|
|
|
|
visible: !transactionListRoot.atYBeginning
|
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
|
2023-06-15 13:09:35 +00:00
|
|
|
repeatTransactionAction.enabled = !overview.isWatchOnlyAccount && delegate.modelData.txType === TransactionDelegate.Send
|
2023-06-13 08:18:53 +00:00
|
|
|
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-06-30 15:07:53 +00:00
|
|
|
required property var model
|
|
|
|
required property int index
|
2023-04-05 11:42:12 +00:00
|
|
|
width: ListView.view.width
|
2023-06-15 13:09:35 +00:00
|
|
|
modelData: model.activityEntry
|
2023-06-30 12:55:59 +00:00
|
|
|
timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000, true) : ""
|
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)
|
|
|
|
}
|
|
|
|
}
|
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 {
|
2023-06-30 13:50:06 +00:00
|
|
|
id: footerColumn
|
|
|
|
readonly property bool allActivityLoaded: !RootStore.historyTransactions.hasMore && transactionListRoot.count !== 0
|
2023-01-12 23:26:48 +00:00
|
|
|
width: root.width
|
|
|
|
spacing: 12
|
2023-06-30 13:50:06 +00:00
|
|
|
|
|
|
|
Separator {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
Layout.topMargin: Style.current.halfPadding
|
|
|
|
visible: d.isInitialLoading
|
|
|
|
}
|
|
|
|
|
|
|
|
StatusTextWithLoadingState {
|
|
|
|
Layout.alignment: Qt.AlignLeft
|
|
|
|
Layout.leftMargin: Style.current.padding
|
|
|
|
text: "01.01.2000"
|
|
|
|
width: d.loadingSectionWidth
|
|
|
|
font.pixelSize: 15
|
|
|
|
loading: visible
|
|
|
|
visible: d.isInitialLoading
|
|
|
|
}
|
|
|
|
|
|
|
|
Repeater {
|
2023-07-07 10:00:19 +00:00
|
|
|
model: !noTxs.visible && (RootStore.historyTransactions.hasMore || d.isInitialLoading) ? 10 : 0
|
2023-06-30 13:50:06 +00:00
|
|
|
TransactionDelegate {
|
|
|
|
Layout.fillWidth: true
|
2023-06-30 15:07:53 +00:00
|
|
|
rootStore: RootStore
|
|
|
|
walletRootStore: WalletStores.RootStore
|
2023-06-30 13:50:06 +00:00
|
|
|
loading: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-31 07:47:52 +00:00
|
|
|
Separator {
|
|
|
|
Layout.topMargin: Style.current.bigPadding
|
|
|
|
Layout.fillWidth: true
|
2023-06-30 13:50:06 +00:00
|
|
|
visible: footerColumn.allActivityLoaded
|
2023-01-12 23:26:48 +00:00
|
|
|
}
|
|
|
|
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
|
2023-06-30 13:50:06 +00:00
|
|
|
visible: footerColumn.allActivityLoaded
|
2023-01-12 23:26:48 +00:00
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
}
|
|
|
|
StatusButton {
|
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
|
|
text: qsTr("Back to most recent transaction")
|
2023-06-30 13:50:06 +00:00
|
|
|
visible: footerColumn.allActivityLoaded && transactionListRoot.contentHeight > transactionListRoot.height
|
2023-01-12 23:26:48 +00:00
|
|
|
onClicked: transactionListRoot.positionViewAtBeginning()
|
|
|
|
}
|
|
|
|
}
|
2022-09-01 15:34:27 +00:00
|
|
|
}
|
2023-07-04 22:29:34 +00:00
|
|
|
|
|
|
|
Component {
|
|
|
|
id: headerComp
|
|
|
|
|
|
|
|
Item {
|
|
|
|
width: root.width
|
|
|
|
height: dataUpdatedButton.implicitHeight
|
|
|
|
|
|
|
|
StatusButton {
|
|
|
|
id: dataUpdatedButton
|
|
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
|
|
text: qsTr("New transactions")
|
|
|
|
|
2023-07-07 14:25:28 +00:00
|
|
|
visible: d.showRefreshButton
|
|
|
|
onClicked: d.refreshData()
|
2023-07-04 22:29:34 +00:00
|
|
|
|
|
|
|
icon.name: "arrow-up"
|
|
|
|
|
|
|
|
radius: 36
|
|
|
|
textColor: Theme.palette.indirectColor1
|
|
|
|
normalColor: Theme.palette.primaryColor1
|
|
|
|
hoverColor: Theme.palette.miscColor1
|
|
|
|
|
|
|
|
size: StatusBaseButton.Size.Tiny
|
|
|
|
}
|
|
|
|
z: 3
|
|
|
|
}
|
|
|
|
}
|
2023-07-07 14:25:28 +00:00
|
|
|
|
|
|
|
Connections {
|
|
|
|
target: RootStore
|
|
|
|
|
|
|
|
function onNewDataAvailableChanged() {
|
|
|
|
if (!d.lastRefreshTime || ((Date.now() - d.lastRefreshTime) > (1000 * d.maxSecondsBetweenRefresh))) {
|
|
|
|
d.showRefreshButton = RootStore.newDataAvailable
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (showRefreshButtonTimer.running) {
|
|
|
|
if (!RootStore.newDataAvailable) {
|
|
|
|
showRefreshButtonTimer.stop()
|
|
|
|
d.showRefreshButton = false
|
|
|
|
}
|
|
|
|
} else if(RootStore.newDataAvailable) {
|
|
|
|
showRefreshButtonTimer.start()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Timer {
|
|
|
|
id: showRefreshButtonTimer
|
|
|
|
|
|
|
|
interval: 2000
|
|
|
|
running: false
|
|
|
|
repeat: false
|
|
|
|
onTriggered: d.showRefreshButton = RootStore.newDataAvailable
|
|
|
|
}
|
2021-10-05 20:50:22 +00:00
|
|
|
}
|