monero-gui/pages/History.qml
2019-06-18 13:30:06 +02:00

1707 lines
73 KiB
QML

// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import QtGraphicalEffects 1.0
import moneroComponents.Wallet 1.0
import moneroComponents.WalletManager 1.0
import moneroComponents.TransactionHistory 1.0
import moneroComponents.TransactionInfo 1.0
import moneroComponents.TransactionHistoryModel 1.0
import moneroComponents.Clipboard 1.0
import FontAwesome 1.0
import "../components/effects/" as MoneroEffects
import "../components" as MoneroComponents
import "../js/Utils.js" as Utils
import "../js/TxUtils.js" as TxUtils
Rectangle {
id: root
property var model
property int sideMargin: 50
property var initialized: false
property int txMax: 5
property int txOffset: 0
property int txPage: (txOffset / 5) + 1
property int txCount: 0
property var sortSearchString: null
property bool sortDirection: true // true = desc, false = asc
property string sortBy: "blockheight"
property var txModelData: [] // representation of transaction data (appWindow.currentWallet.historyModel)
property var txData: [] // representation of FILTERED transation data
property var txDataCollapsed: [] // keep track of which txs are collapsed
property string historyStatusMessage: ""
property alias contentHeight: pageRoot.height
Clipboard { id: clipboard }
ListModel { id: txListViewModel }
color: "transparent"
ColumnLayout {
id: pageRoot
anchors.topMargin: 40
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
RowLayout {
Layout.preferredHeight: 24
Layout.preferredWidth: parent.width - root.sideMargin
Layout.leftMargin: sideMargin
Layout.rightMargin: sideMargin
Layout.bottomMargin: 10
MoneroComponents.Label {
fontSize: 24
text: qsTr("Transactions") + translationManager.emptyString
}
Item {
Layout.fillWidth: true
}
RowLayout {
id: sortAndFilter
visible: root.txCount > 0
property bool collapsed: false
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.preferredWidth: 100
Layout.preferredHeight: 15
spacing: 8
MoneroComponents.TextPlain {
Layout.alignment: Qt.AlignVCenter
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Sort & filter") + translationManager.emptyString
color: MoneroComponents.Style.defaultFontColor
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
sortAndFilter.collapsed = !sortAndFilter.collapsed
}
}
}
MoneroEffects.ImageMask {
id: sortCollapsedIcon
Layout.alignment: Qt.AlignVCenter
height: 8
width: 12
image: "qrc:///images/whiteDropIndicator.png"
fontAwesomeFallbackIcon: FontAwesome.arrowDown
fontAwesomeFallbackSize: 14
rotation: sortAndFilter.collapsed ? 0 : 180
color: MoneroComponents.Style.defaultFontColor
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
sortAndFilter.collapsed = !sortAndFilter.collapsed
}
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: sideMargin
Layout.rightMargin: sideMargin
visible: sortAndFilter.collapsed
MoneroComponents.LineEdit {
id: searchInput
Layout.fillWidth: true
input.topPadding: 6
input.bottomPadding: 6
fontSize: 16
labelFontSize: 14
placeholderText: qsTr("Search...") + translationManager.emptyString
placeholderFontSize: 16
inputHeight: 34
onTextUpdated: {
if(searchInput.text != null && searchInput.text.length >= 3){
root.sortSearchString = searchInput.text;
root.reset();
root.updateFilter();
} else {
root.sortSearchString = null;
root.reset();
root.updateFilter();
}
}
}
}
GridLayout {
visible: sortAndFilter.collapsed
Layout.fillWidth: true
Layout.topMargin: 4
Layout.leftMargin: sideMargin
Layout.rightMargin: sideMargin
columns: 2
columnSpacing: 20
z: 6
MoneroComponents.DatePicker {
id: fromDatePicker
Layout.fillWidth: true
width: 100
inputLabel.text: qsTr("Date from") + translationManager.emptyString
inputLabel.font.pixelSize: 14
onCurrentDateChanged: {
if(root.initialized){
root.reset();
root.updateFilter();
}
}
}
MoneroComponents.DatePicker {
id: toDatePicker
Layout.fillWidth: true
width: 100
inputLabel.text: qsTr("Date to") + translationManager.emptyString
onCurrentDateChanged: {
if(root.initialized){
root.reset();
root.updateFilter();
}
}
}
}
RowLayout {
Layout.topMargin: 20
Layout.bottomMargin: 20
Layout.fillWidth: true
Layout.leftMargin: sideMargin
Layout.rightMargin: sideMargin
Rectangle {
visible: sortAndFilter.collapsed
color: "transparent"
Layout.preferredWidth: childrenRect.width + 38
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Sort by") + ":"
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
visible: sortAndFilter.collapsed
id: sortBlockheight
color: "transparent"
Layout.preferredWidth: sortBlockheightText.width + 42
Layout.preferredHeight: 20
RowLayout {
clip: true
anchors.fill: parent
MoneroComponents.TextPlain {
id: sortBlockheightText
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Blockheight") + translationManager.emptyString
color: root.sortBy === "blockheight" ? MoneroComponents.Style.defaultFontColor : MoneroComponents.Style.dimmedFontColor
anchors.verticalCenter: parent.verticalCenter
themeTransition: false
}
MoneroEffects.ImageMask {
height: 8
width: 12
visible: root.sortBy === "blockheight" ? true : false
opacity: root.sortBy === "blockheight" ? 1 : 0.2
image: "qrc:///images/whiteDropIndicator.png"
fontAwesomeFallbackIcon: FontAwesome.arrowDown
fontAwesomeFallbackSize: 14
color: MoneroComponents.Style.defaultFontColor
rotation: {
if(root.sortBy === "blockheight"){
return root.sortDirection ? 0 : 180
} else {
return 0;
}
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
if(root.sortBy !== "blockheight") {
root.sortDirection = true;
} else {
root.sortDirection = !root.sortDirection
}
root.sortBy = "blockheight";
root.updateSort();
}
}
}
Rectangle {
visible: sortAndFilter.collapsed
color: "transparent"
Layout.preferredWidth: sortDateText.width + 42
Layout.preferredHeight: 20
RowLayout {
clip: true
anchors.fill: parent
MoneroComponents.TextPlain {
id: sortDateText
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Date") + translationManager.emptyString
color: root.sortBy === "timestamp" ? MoneroComponents.Style.defaultFontColor : MoneroComponents.Style.dimmedFontColor
themeTransition: false
anchors.verticalCenter: parent.verticalCenter
}
MoneroEffects.ImageMask {
height: 8
width: 12
visible: root.sortBy === "timestamp" ? true : false
opacity: root.sortBy === "timestamp" ? 1 : 0.2
image: "qrc:///images/whiteDropIndicator.png"
fontAwesomeFallbackIcon: FontAwesome.arrowDown
fontAwesomeFallbackSize: 14
color: MoneroComponents.Style.defaultFontColor
rotation: {
if(root.sortBy === "timestamp"){
return root.sortDirection ? 0 : 180
} else {
return 0;
}
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
if(root.sortBy !== "timestamp") {
root.sortDirection = true;
} else {
root.sortDirection = !root.sortDirection
}
root.sortBy = "timestamp";
root.updateSort();
}
}
}
Rectangle {
visible: sortAndFilter.collapsed
color: "transparent"
Layout.preferredWidth: sortAmountText.width + 42
Layout.preferredHeight: 20
RowLayout {
clip: true
anchors.fill: parent
MoneroComponents.TextPlain {
id: sortAmountText
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Amount") + translationManager.emptyString
color: root.sortBy === "amount" ? MoneroComponents.Style.defaultFontColor : MoneroComponents.Style.dimmedFontColor
themeTransition: false
anchors.verticalCenter: parent.verticalCenter
}
MoneroEffects.ImageMask {
height: 8
width: 12
visible: root.sortBy === "amount" ? true : false
opacity: root.sortBy === "amount" ? 1 : 0.2
image: "qrc:///images/whiteDropIndicator.png"
fontAwesomeFallbackIcon: FontAwesome.arrowDown
fontAwesomeFallbackSize: 14
color: MoneroComponents.Style.defaultFontColor
rotation: {
if(root.sortBy === "amount"){
return root.sortDirection ? 0 : 180
} else {
return 0;
}
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
if(root.sortBy !== "amount") {
root.sortDirection = true;
} else {
root.sortDirection = !root.sortDirection
}
root.sortBy = "amount";
root.updateSort();
}
}
}
Rectangle {
visible: !sortAndFilter.collapsed
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
// status message
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: root.historyStatusMessage
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
}
}
Item {
Layout.fillWidth: true
}
RowLayout {
id: pagination
visible: root.txCount > 0
spacing: 0
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: childrenRect.width
Layout.preferredHeight: 20
Rectangle {
color: "transparent"
Layout.preferredWidth: childrenRect.width + 2
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Page") + ":"
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.preferredWidth: childrenRect.width + 10
Layout.leftMargin: 4
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
id: paginationText
text: root.txPage + "/" + Math.ceil(root.txCount / root.txMax)
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
// jump to page functionality
property int pages: Math.ceil(root.txCount / root.txMax)
anchors.fill: parent
hoverEnabled: pages > 1
cursorShape: hoverEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
onClicked: {
if(pages === 1)
return;
inputDialog.labelText = qsTr("Jump to page (1-%1)").arg(pages) + translationManager.emptyString;
inputDialog.inputText = "1";
inputDialog.onAcceptedCallback = function() {
var pageNumber = parseInt(inputDialog.inputText);
if (!isNaN(pageNumber) && pageNumber >= 1 && pageNumber <= pages) {
root.paginationJump(parseInt(pageNumber));
return;
}
appWindow.showStatusMessage(qsTr("Invalid page. Must be a number within the specified range."), 4);
}
inputDialog.onRejectedCallback = null;
inputDialog.open()
}
}
}
}
Rectangle {
id: paginationPrev
Layout.preferredWidth: 18
Layout.preferredHeight: 20
color: "transparent"
opacity: enabled ? 1.0 : 0.2
enabled: false
MoneroEffects.ImageMask {
id: prevIcon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
height: 8
width: 12
image: "qrc:///images/whiteDropIndicator.png"
fontAwesomeFallbackIcon: FontAwesome.arrowDown
fontAwesomeFallbackSize: 14
color: MoneroComponents.Style.defaultFontColor
rotation: 90
}
MouseArea {
enabled: parent.enabled
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.paginationPrevClicked();
}
}
}
Rectangle {
id: paginationNext
Layout.preferredWidth: 18
Layout.preferredHeight: 20
color: "transparent"
opacity: enabled ? 1.0 : 0.2
enabled: false
MoneroEffects.ImageMask {
id: nextIcon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
height: 8
width: 12
image: "qrc:///images/whiteDropIndicator.png"
fontAwesomeFallbackIcon: FontAwesome.arrowDown
fontAwesomeFallbackSize: 14
rotation: 270
color: MoneroComponents.Style.defaultFontColor
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.paginationNextClicked();
}
}
}
}
}
ListView {
visible: true
id: txListview
Layout.fillWidth: true
Layout.preferredHeight: contentHeight;
model: txListViewModel
interactive: false
delegate: Rectangle {
id: delegate
property bool collapsed: root.txDataCollapsed.indexOf(hash) >= 0 ? true : false
anchors.left: parent.left
anchors.right: parent.right
height: {
if(!collapsed) return 60;
if(isout && delegate.address !== "") return 320;
return 220;
}
color: {
if(!collapsed) return "transparent"
return MoneroComponents.Style.blackTheme ? "#06FFFFFF" : "#04000000"
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: sideMargin
color: "transparent"
Rectangle {
anchors.top: parent.top
anchors.topMargin: 24
anchors.horizontalCenter: parent.horizontalCenter
width: 10
height: 10
radius: 8
color: isout ? "#d85a00" : "#2eb358"
}
}
ColumnLayout {
spacing: 0
clip: true
height: parent.height
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: sideMargin
anchors.rightMargin: sideMargin
RowLayout {
spacing: 0
Layout.fillWidth: true
height: 60
Layout.preferredHeight: 60
ColumnLayout {
spacing: 0
clip: true
Layout.preferredHeight: 120
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: isout ? qsTr("Sent") : qsTr("Received") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: _amount + " XMR"
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: isout ? qsTr("Fee") : confirmationsRequired === 60 ? qsTr("Mined") : qsTr("Fee") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: {
if(!isout && confirmationsRequired === 60) return qsTr("Yes") + translationManager.emptyString;
if(fee !== "") return fee + " XMR";
return "-";
}
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
}
ColumnLayout {
spacing: 0
clip: true
Layout.preferredHeight: 120
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Blockheight") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 14
text: blockheight > 0 ? blockheight : qsTr('Pending') + translationManager.emptyString;
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Confirmations") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
property bool confirmed: confirmations < confirmationsRequired ? false : true
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: confirmed ? confirmations : confirmations + "/" + confirmationsRequired
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
}
ColumnLayout {
spacing: 0
clip: true
Layout.preferredHeight: 120
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Date")
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: persistentSettings.historyHumanDates ? dateHuman : date + " " + time
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable"
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.color = MoneroComponents.Style.orange
if (persistentSettings.historyHumanDates) {
parent.text = date + " " + time;
}
}
onExited: {
parent.color = MoneroComponents.Style.defaultFontColor
if (persistentSettings.historyHumanDates) {
parent.text = dateHuman
}
}
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 60
MoneroComponents.StandardButton {
id: btnDetails
text: FontAwesome.info
small: true
label.font.family: FontAwesome.fontFamily
fontSize: 18
width: 28
MouseArea {
state: "details"
anchors.fill: parent
hoverEnabled: true
z: parent.z + 1
onEntered: parent.opacity = 0.8;
onExited: parent.opacity = 1.0;
}
}
Image {
visible: !isout && confirmationsRequired === 60
anchors.left: btnDetails.right
anchors.leftMargin: 16
width: 28
height: 28
source: "qrc:///images/miningxmr.png"
}
MoneroComponents.StandardButton {
visible: isout
anchors.left: btnDetails.right
anchors.leftMargin: 10
text: FontAwesome.productHunt
small: true
label.font.family: FontAwesome.fontFamily
fontSize: 18
width: 36
MouseArea {
state: "proof"
anchors.fill: parent
hoverEnabled: true
z: parent.z + 1
onEntered: parent.opacity = 0.8;
onExited: parent.opacity = 1.0;
}
}
}
}
}
ColumnLayout {
spacing: 0
Layout.fillWidth: true
Layout.preferredHeight: 40
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Description") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
id: txNoteText
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: tx_note !== "" ? tx_note : "-"
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
MoneroEffects.ImageMask {
anchors.top: parent.top
anchors.left: txNoteText.right
anchors.leftMargin: 12
image: "qrc:///images/edit.svg"
fontAwesomeFallbackIcon: FontAwesome.pencilSquare
fontAwesomeFallbackSize: 22
color: MoneroComponents.Style.defaultFontColor
opacity: 0.75
width: 23
height: 21
MouseArea {
id: txNoteArea
state: "set_tx_note"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.opacity = 0.4;
onExited: parent.opacity = 0.75;
cursorShape: Qt.PointingHandCursor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Transaction ID") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: hash
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Transaction key") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Click to reveal")
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
state: "txkey_hidden"
MouseArea {
state: "copyable_txkey"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
Rectangle {
visible: isout
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: qsTr("Address sent to") + translationManager.emptyString
color: MoneroComponents.Style.historyHeaderTextColor
themeTransitionBlackColor: MoneroComponents.Style._b_historyHeaderTextColor
themeTransitionWhiteColor: MoneroComponents.Style._w_historyHeaderTextColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
visible: isout
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 20
MoneroComponents.TextPlain {
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: {
if(isout && address !== ""){
return TxUtils.addressTruncate(address, 24);
}
if(isout && blockheight === 0)
return qsTr("Waiting for transaction to leave txpool.") + translationManager.emptyString
else
return qsTr("Unknown recipient") + translationManager.emptyString;
}
color: MoneroComponents.Style.defaultFontColor
anchors.verticalCenter: parent.verticalCenter
MouseArea {
state: "copyable_address"
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = MoneroComponents.Style.orange
onExited: parent.color = MoneroComponents.Style.defaultFontColor
}
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: 10
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
MouseArea {
id: collapseArea
objectName: "collapseArea"
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
// detect clicks on text (for copying), otherwise toggle collapse
var doCollapse = true;
var res = Utils.qmlEach(delegate, ['containsMouse', 'preventStealing', 'scrollGestureEnabled'], ['collapseArea'], []);
for(var i = 0; i < res.length; i+=1){
if(res[i].containsMouse === true){
if(res[i].state === 'copyable' && res[i].parent.hasOwnProperty('text')) toClipboard(res[i].parent.text);
if(res[i].state === 'copyable_address') root.toClipboard(address);
if(res[i].state === 'copyable_txkey') root.getTxKey(hash, res[i]);
if(res[i].state === 'set_tx_note') root.editDescription(hash);
if(res[i].state === 'details') root.showTxDetails(hash, paymentId, destinations, subaddrAccount, subaddrIndex);
if(res[i].state === 'proof') root.showTxProof(hash, paymentId, destinations, subaddrAccount, subaddrIndex);
doCollapse = false;
break;
}
}
if(doCollapse){
collapsed = !collapsed;
// remember collapsed state
if(collapsed){
root.txDataCollapsed.push(hash);
} else {
root.removeFromCollapsedList(hash);
}
}
}
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: sideMargin
color: "transparent"
MoneroEffects.ImageMask {
id: collapsedIcon
anchors.top: parent.top
anchors.topMargin: 24
anchors.horizontalCenter: parent.horizontalCenter
height: 8
width: 12
image: "qrc:///images/whiteDropIndicator.png"
rotation: delegate.collapsed ? 180 : 0
color: MoneroComponents.Style.defaultFontColor
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: 1
color: MoneroComponents.Style.appWindowBorderColor
MoneroEffects.ColorTransition {
targetObj: parent
blackColor: MoneroComponents.Style._b_appWindowBorderColor
whiteColor: MoneroComponents.Style._w_appWindowBorderColor
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.bottom
height: 1
color: MoneroComponents.Style.appWindowBorderColor
MoneroEffects.ColorTransition {
targetObj: parent
blackColor: MoneroComponents.Style._b_appWindowBorderColor
whiteColor: MoneroComponents.Style._w_appWindowBorderColor
}
}
}
}
Item {
visible: sortAndFilter.collapsed
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: sideMargin
Layout.rightMargin: sideMargin
MoneroComponents.TextPlain {
// status message
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 15
text: root.historyStatusMessage;
color: MoneroComponents.Style.dimmedFontColor
themeTransitionBlackColor: MoneroComponents.Style._b_dimmedFontColor
themeTransitionWhiteColor: MoneroComponents.Style._w_dimmedFontColor
}
}
MoneroComponents.CheckBox2 {
id: showAdvancedCheckbox
Layout.topMargin: 30
Layout.bottomMargin: 20
Layout.leftMargin: sideMargin
Layout.rightMargin: sideMargin
checked: persistentSettings.historyShowAdvanced
onClicked: persistentSettings.historyShowAdvanced = !persistentSettings.historyShowAdvanced
text: qsTr("Advanced options") + translationManager.emptyString
}
ColumnLayout {
visible: persistentSettings.historyShowAdvanced
Layout.leftMargin: sideMargin
Layout.rightMargin: sideMargin
spacing: 20
MoneroComponents.CheckBox {
id: humanDatesCheckBox
checked: persistentSettings.historyHumanDates
onClicked: {
persistentSettings.historyHumanDates = !persistentSettings.historyHumanDates
root.updateDisplay(root.txOffset, root.txMax, false);
}
text: qsTr("Human readable date format") + translationManager.emptyString
}
MoneroComponents.StandardButton {
visible: !isIOS && root.txCount > 0
small: true
text: qsTr("Export all history") + translationManager.emptyString
onClicked: {
writeCSVFileDialog.open();
}
}
}
}
function refresh(){
if(appWindow.currentWallet != null && typeof appWindow.currentWallet.history !== "undefined" ) {
currentWallet.history.refresh(currentWallet.currentSubaddressAccount);
}
if (typeof root.model !== 'undefined' && root.model != null) {
toDatePicker.currentDate = root.model.transactionHistory.lastDateTime
}
// extract from model, create JS array of txs
root.updateTransactionsFromModel();
// fill listview, update UI
root.updateDisplay(root.txOffset, root.txMax);
}
function reset() {
root.txOffset = 0;
root.txMax = 5;
if (typeof root.model !== 'undefined' && root.model != null) {
root.model.dateFromFilter = "2014-04-18" // genesis block
root.model.dateToFilter = "9999-09-09" // fix before september 9999
// negative values disable filters here;
root.model.amountFromFilter = -1;
root.model.amountToFilter = -1;
root.model.directionFilter = TransactionInfo.Direction_Both;
}
}
function updateFilter(){
// applying filters
root.txData = JSON.parse(JSON.stringify(root.txModelData)); // deepcopy
var fromDate = fromDatePicker.currentDate.getTime() / 1000;
var toDate = toDatePicker.currentDate.getTime() / 1000;
var txs = [];
for (var i = 0; i < root.txData.length; i++){
var item = root.txData[i];
var matched = "";
// daterange filtering
if(item.timestamp < fromDate || item.timestamp > toDate){
continue;
}
// search string filtering
if(root.sortSearchString == null || root.sortSearchString === ""){
txs.push(root.txData[i]);
continue;
}
if(root.sortSearchString.length >= 1){
if(item.amount && item.amount.toString().startsWith(root.sortSearchString)){
txs.push(item);
} else if(item.address !== "" && item.address.startsWith(root.sortSearchString)){
txs.push(item);
} else if(item.blockheight.toString().startsWith(root.sortSearchString)) {
txs.push(item);
} else if (item.hash.startsWith(root.sortSearchString)){
txs.push(item);
}
}
}
root.txData = txs;
root.txCount = root.txData.length;
root.updateSort();
root.updateDisplay(root.txOffset, root.txMax);
}
function updateSort(){
// applying sorts
root.txOffset = 0;
root.txData.sort(function(a, b) {
return a[root.sortBy] - b[root.sortBy];
});
if(root.sortDirection)
root.txData.reverse();
root.updateDisplay(root.txOffset, root.txMax);
}
function updateDisplay(tx_offset, tx_max, auto_collapse) {
if(typeof auto_collapse === 'undefined') auto_collapse = false;
txListViewModel.clear();
// limit results as per tx_max (root.txMax)
var txs = root.txData.slice(tx_offset, tx_offset + tx_max);
// make first result on the first page collapsed by default
if(auto_collapse && root.txPage === 1 && txs.length > 0 && (root.sortSearchString == null || root.sortSearchString === ""))
root.txDataCollapsed.push(txs[0]['hash']);
// populate listview
for (var i = 0; i < txs.length; i++){
txListViewModel.append(txs[i]);
}
root.updateHistoryStatusMessage();
// determine pagination button states
var count = txData.length;
if(count <= root.txMax) {
paginationPrev.enabled = false;
paginationNext.enabled = false;
return;
}
if(root.txOffset < root.txMax)
paginationPrev.enabled = false;
else
paginationPrev.enabled = true;
if((root.txOffset + root.txMax) >= count)
paginationNext.enabled = false;
else
paginationNext.enabled = true;
}
function updateTransactionsFromModel() {
// This function copies the items of `appWindow.currentWallet.historyModel` to `root.txModelData`, as a list of javascript objects
if(currentWallet == null || typeof currentWallet.history === "undefined" ) return;
var _model = root.model;
var total = 0
var count = _model.rowCount()
root.txModelData = [];
for (var i = 0; i < count; ++i) {
var idx = _model.index(i, 0);
var isout = _model.data(idx, TransactionHistoryModel.TransactionIsOutRole);
var amount = _model.data(idx, TransactionHistoryModel.TransactionAmountRole);
var hash = _model.data(idx, TransactionHistoryModel.TransactionHashRole);
var paymentId = _model.data(idx, TransactionHistoryModel.TransactionPaymentIdRole);
var destinations = _model.data(idx, TransactionHistoryModel.TransactionDestinationsRole);
var time = _model.data(idx, TransactionHistoryModel.TransactionTimeRole);
var date = _model.data(idx, TransactionHistoryModel.TransactionDateRole);
var blockheight = _model.data(idx, TransactionHistoryModel.TransactionBlockHeightRole);
var confirmations = _model.data(idx, TransactionHistoryModel.TransactionConfirmationsRole);
var confirmationsRequired = _model.data(idx, TransactionHistoryModel.TransactionConfirmationsRequiredRole);
var fee = _model.data(idx, TransactionHistoryModel.TransactionFeeRole);
var subaddrAccount = model.data(idx, TransactionHistoryModel.TransactionSubaddrAccountRole);
var subaddrIndex = model.data(idx, TransactionHistoryModel.TransactionSubaddrIndexRole);
var timestamp = new Date(date + " " + time).getTime() / 1000;
var dateHuman = Utils.ago(timestamp);
var _amount = amount;
if(_amount === 0){
// *sometimes* amount is 0, while the 'destinations string'
// has the correct amount, so we try to fetch it from that instead.
_amount = TxUtils.destinationsToAmount(destinations);
_amount = Number(_amount *1);
}
var tx_note = currentWallet.getUserNote(hash);
var address = "";
if(isout) {
address = TxUtils.destinationsToAddress(destinations);
}
if (isout)
total = walletManager.subi(total, amount)
else
total = walletManager.addi(total, amount)
root.txModelData.push({
"i": i,
"isout": isout,
"amount": Number(amount),
"_amount": _amount,
"hash": hash,
"paymentId": paymentId,
"address": address,
"destinations": destinations,
"tx_note": tx_note,
"time": time,
"date": date,
"dateHuman": dateHuman,
"blockheight": blockheight,
"address": address,
"timestamp": timestamp,
"fee": fee,
"confirmations": confirmations,
"confirmationsRequired": confirmationsRequired,
"subaddrAccount": subaddrAccount,
"subaddrIndex": subaddrIndex
});
}
root.txData = JSON.parse(JSON.stringify(root.txModelData)); // deepcopy
root.txCount = root.txData.length;
}
function update() {
// handle outside mutation of tx model; incoming/outgoing funds or new blocks. Update table.
currentWallet.history.refresh(currentWallet.currentSubaddressAccount);
root.updateTransactionsFromModel();
root.updateFilter();
}
function editDescription(_hash){
inputDialog.labelText = qsTr("Set description:") + translationManager.emptyString;
inputDialog.onAcceptedCallback = function() {
appWindow.currentWallet.setUserNote(_hash, inputDialog.inputText);
appWindow.showStatusMessage(qsTr("Updated description."),3);
root.update();
}
inputDialog.onRejectedCallback = null;
inputDialog.open();
}
function paginationPrevClicked(){
root.txOffset -= root.txMax;
updateDisplay(root.txOffset, root.txMax);
}
function paginationNextClicked(){
root.txOffset += root.txMax;
updateDisplay(root.txOffset, root.txMax);
}
function paginationJump(pageNumber){
root.txOffset = root.txMax * Math.ceil(pageNumber - 1 || 0);
updateDisplay(root.txOffset, root.txMax);
}
function removeFromCollapsedList(hash){
root.txDataCollapsed = root.txDataCollapsed.filter(function(item) {
return item !== hash
});
}
function updateHistoryStatusMessage(){
if(root.txModelData.length <= 0){
root.historyStatusMessage = qsTr("No transaction history yet.") + translationManager.emptyString;
} else if (root.txData.length <= 0){
root.historyStatusMessage = qsTr("No results.") + translationManager.emptyString;
} else {
root.historyStatusMessage = qsTr("%1 transactions total, showing %2.").arg(root.txData.length).arg(txListViewModel.count) + translationManager.emptyString;
}
}
function getTxKey(hash, elem){
if (elem.parent.state != 'ready'){
var txKey = currentWallet.getTxKey(hash)
elem.parent.text = txKey ? txKey : '-';
elem.parent.state = 'ready';
}
toClipboard(elem.parent.text);
}
function showTxDetails(hash, paymentId, destinations, subaddrAccount, subaddrIndex){
var tx_key = currentWallet.getTxKey(hash)
var tx_note = currentWallet.getUserNote(hash)
var rings = currentWallet.getRings(hash)
var address_label = subaddrIndex == 0 ? (qsTr("Primary address") + translationManager.emptyString) : currentWallet.getSubaddressLabel(subaddrAccount, subaddrIndex)
var address = currentWallet.address(subaddrAccount, subaddrIndex)
if (rings)
rings = rings.replace(/\|/g, '\n')
informationPopup.title = qsTr("Transaction details") + translationManager.emptyString;
informationPopup.content = buildTxDetailsString(hash, paymentId, tx_key, tx_note, destinations, rings, address, address_label);
informationPopup.onCloseCallback = null
informationPopup.open();
}
function showTxProof(hash, paymentId, destinations, subaddrAccount, subaddrIndex){
var address = TxUtils.destinationsToAddress(destinations);
if(address === undefined){
console.log('getProof: Error fetching address')
return;
}
var checked = (TxUtils.checkTxID(hash) && TxUtils.checkAddress(address, appWindow.persistentSettings.nettype));
if(!checked){
console.log('getProof: Error checking TxId and/or address');
}
console.log("getProof: Generate clicked: txid " + hash + ", address " + address);
middlePanel.getProofClicked(hash, address, '');
}
function toClipboard(text){
console.log("Copied to clipboard");
clipboard.setText(text);
appWindow.showStatusMessage(qsTr("Copied to clipboard"),3);
}
function buildTxDetailsString(tx_id, paymentId, tx_key,tx_note, destinations, rings, address, address_label) {
var trStart = '<tr><td width="85" style="padding-top:5px"><b>',
trMiddle = '</b></td><td style="padding-left:10px;padding-top:5px;">',
trEnd = "</td></tr>";
return '<table border="0">'
+ (tx_id ? trStart + qsTr("Tx ID:") + trMiddle + tx_id + trEnd : "")
+ (address_label ? trStart + qsTr("Address label:") + trMiddle + address_label + trEnd : "")
+ (address ? trStart + qsTr("Address:") + trMiddle + address + trEnd : "")
+ (paymentId ? trStart + qsTr("Payment ID:") + trMiddle + paymentId + trEnd : "")
+ (tx_key ? trStart + qsTr("Tx key:") + trMiddle + tx_key + trEnd : "")
+ (tx_note ? trStart + qsTr("Tx note:") + trMiddle + tx_note + trEnd : "")
+ (destinations ? trStart + qsTr("Destinations:") + trMiddle + destinations + trEnd : "")
+ (rings ? trStart + qsTr("Rings:") + trMiddle + rings + trEnd : "")
+ "</table>"
+ translationManager.emptyString;
}
function lookupPaymentID(paymentId) {
if (!addressBookModel)
return ""
var idx = addressBookModel.lookupPaymentID(paymentId)
if (idx < 0)
return ""
idx = addressBookModel.index(idx, 0)
return addressBookModel.data(idx, AddressBookModel.AddressBookDescriptionRole)
}
FileDialog {
id: writeCSVFileDialog
title: qsTr("Please choose a folder") + translationManager.emptyString
selectFolder: true
onRejected: {
console.log("csv write canceled")
}
onAccepted: {
var dataDir = walletManager.urlToLocalPath(writeCSVFileDialog.fileUrl);
var written = currentWallet.history.writeCSV(currentWallet.currentSubaddressAccount, dataDir);
if(written !== ""){
informationPopup.title = qsTr("Success") + translationManager.emptyString;
var text = qsTr("CSV file written to: %1").arg(written) + "\n\n"
text += qsTr("Tip: Use your favorite spreadsheet software to sort on blockheight.") + "\n\n" + translationManager.emptyString;
informationPopup.text = text;
informationPopup.icon = StandardIcon.Information;
} else {
informationPopup.title = qsTr("Error") + translationManager.emptyString;
informationPopup.text = qsTr("Error exporting transaction data.") + "\n\n" + translationManager.emptyString;
informationPopup.icon = StandardIcon.Critical;
}
informationPopup.onCloseCallback = null;
informationPopup.open();
}
Component.onCompleted: {
var _folder = 'file://' + moneroAccountsDir;
try {
_folder = 'file://' + desktopFolder;
}
catch(err) {}
finally {
writeCSVFileDialog.folder = _folder;
}
}
}
function onPageCompleted() {
// setup date filter scope according to real transactions
if(appWindow.currentWallet != null){
root.model = appWindow.currentWallet.historyModel;
root.model.sortRole = TransactionHistoryModel.TransactionBlockHeightRole
root.model.sort(0, Qt.DescendingOrder);
fromDatePicker.currentDate = model.transactionHistory.firstDateTime
}
root.reset();
root.refresh();
root.initialized = true;
}
function onPageClosed(){
root.initialized = false;
root.reset();
}
}