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-01-12 23:26:48 +00:00
QtObject {
id: d
property bool isLoading: false
2023-06-15 17:43:08 +00:00
property var activityFiltersStore: WalletStores . ActivityFiltersStore { }
2023-01-12 23:26:48 +00:00
}
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-06-15 17:43:08 +00:00
visible: ! d . isLoading && transactionListRoot . count === 0 && ! d . activityFiltersStore . filtersSet
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
// Tp-do make connections with nim once logic is ready
ActivityFilterPanel {
2023-06-02 18:00:31 +00:00
id: filterComponent
2023-06-15 17:43:08 +00:00
visible: ! d . isLoading && ( transactionListRoot . count !== 0 || d . activityFiltersStore . filtersSet )
2023-06-02 18:00:31 +00:00
Layout.fillWidth: true
Layout.preferredHeight: 50
2023-06-15 17:43:08 +00:00
activityFilterStore: d . activityFiltersStore
store: WalletStores . RootStore
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
property string firstSection
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
property var formatDate: ( ms ) = > LocaleUtils . formatDate ( ms , Locale . ShortFormat )
proxyRoles: ExpressionRole {
name: "date"
expression: {
return model . activityEntry . timestamp > 0 ? txModel . formatDate ( model . activityEntry . 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-06-16 09:37:17 +00:00
delegate: transactionDelegate
2023-03-23 07:34:05 +00:00
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
displaced: Transition { // TODO Remove animation when ordered fetching of transactions is implemented
NumberAnimation { properties: "y" ; duration: 250 ; easing.type: Easing . Linear ; alwaysRunToEnd: true }
}
2023-03-23 07:34:05 +00:00
2023-06-16 09:37:17 +00:00
readonly property point lastVisibleItemPos: Qt . point ( 0 , contentY + height - 1 )
property int lastVisibleIndex: indexAt ( lastVisibleItemPos . x , lastVisibleItemPos . y )
onCountChanged: {
// 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
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-03-23 07:34:05 +00:00
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-06-16 09:37:17 +00:00
onMovingVerticallyChanged: {
if ( ! userScrolled ) {
userScrolled = true
currentIndex = Qt . binding ( ( ) = > lastVisibleIndex >= 0 ? lastVisibleIndex : ( count - 1 ) )
2023-03-23 07:34:05 +00:00
}
2023-06-16 09:37:17 +00:00
lastVisibleIndex = indexAt ( lastVisibleItemPos . x , lastVisibleItemPos . y )
2023-03-23 07:34:05 +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"
topMargin: - 20 // Top margin for first section. Section cannot have different sizes
section.delegate: ColumnLayout {
id: sectionDelegate
2023-04-05 11:42:12 +00:00
2023-06-16 09:37:17 +00:00
readonly property bool isFirstSection: ListView . view . firstSection === section
2023-04-05 11:42:12 +00:00
2023-06-16 09:37:17 +00:00
width: ListView . view . width
// display always first section. Other sections when more items are being fetched must not be visible
// 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
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
Layout.topMargin: 20
implicitHeight: 1
2023-04-05 11:42:12 +00:00
}
2023-06-16 09:37:17 +00:00
StatusTextWithLoadingState {
id: sectionText
Layout.alignment: Qt . AlignBottom
leftPadding: 16
bottomPadding: 8
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
font.pixelSize: 13
verticalAlignment: Qt . AlignBottom
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
}
2023-04-05 11:42:12 +00:00
}
2023-06-16 09:37:17 +00:00
return false
2023-04-05 11:42:12 +00:00
}
}
2023-03-23 07:34:05 +00:00
}
2023-06-16 09:37:17 +00:00
onAtYEndChanged: if ( atYEnd ) RootStore . fetchMoreTransactions ( )
}
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-04-05 11:42:12 +00:00
width: ListView . view . width
2023-06-15 13:09:35 +00:00
modelData: model . activityEntry
2023-02-17 12:56:31 +00:00
currentCurrency: RootStore . currentCurrency
2023-06-14 19:53:13 +00:00
cryptoValue: isModelDataValid ? modelData.value : 0.0
2023-04-05 11:42:12 +00:00
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 : ""
2023-06-15 13:09:35 +00:00
transactionStatus: isModelDataValid ? modelData.status : 0
2023-05-10 11:54:06 +00:00
timeStampText: isModelDataValid ? LocaleUtils . formatRelativeTimestamp ( modelData . timestamp * 1000 ) : ""
2023-06-15 13:09:35 +00:00
addressNameTo: isModelDataValid ? WalletStores . RootStore . getNameForAddress ( modelData . recipient ) : ""
addressNameFrom: isModelDataValid ? WalletStores . RootStore . getNameForAddress ( modelData . sender ) : ""
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-06-15 13:09:35 +00:00
loading: false // TODO handle loading state
2023-04-05 11:42:12 +00:00
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
}
2021-10-05 20:50:22 +00:00
}