/* * This file is part of the Flowee project * Copyright (C) 2023-2026 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee Item { id: root property bool active: false // true when this is the current tab property bool hideQRScanButton: backToTopButton.y > 0 Column { id: header property int leeway: 30 Behavior on leeway { NumberAnimation {} } width: parent.width y: { if (!accountSyncState.hasProgressbar && accountSyncState.implicitHeight > 0) return leeway - accountSyncState.initialHeight / 4 - 8 return leeway } spacing: { if (!accountSyncState.hasProgressbar && accountSyncState.implicitHeight > 0) return accountSyncState.initialHeight / -4 + 4 return 0 } Flowee.BitcoinAmountLabel { id: bchPriceWidget opacity: Pay.hideBalance ? 0.2 : 1 fontPixelSize: 30 anchors.horizontalCenter: parent.horizontalCenter value: { if (Pay.hideBalance) return 88888888 return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed } colorize: false showFiat: false } Flowee.Label { // fiat price opacity: Pay.hideBalance ? 0.2 : 1 width: parent.width horizontalAlignment: Text.AlignHCenter text: { if (Pay.hideBalance) return Fiat.currencySymbolPrefix + "——" + Fiat.currencySymbolPost Fiat.formattedPrice(portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed, Fiat.price) } MouseArea { // make the click area nice and large width: parent.width height: parent.height + bchPriceWidget.height + 10 y: 0 - 5- bchPriceWidget.height onClicked: popupOverlay.open(priceDetails, parent) cursorShape: Qt.PointingHandCursor } Component { id: priceDetails PriceDetails { } } } AccountSyncState { id: accountSyncState account: portfolio.current width: parent.width property int initialHeight: -1 Component.onCompleted: initialHeight = implicitHeight } Item { width: 1; height: parent.leeway + 1 } // spacer } ListView { id: listView width: parent.width anchors.top: header.bottom anchors.bottom: parent.bottom onContentYChanged: { if (count === 0) return 30 let i = itemAtIndex(0) if (i === null) return 0 let lw = contentY - i.y + 30 if (lw > 0) lw = Math.max(0, lw - 30) lw = Math.min(30, lw) lw = Math.max(-10, lw) var curHint = header.leeway if ((curHint === 0 && lw < 0) || curHint !== 0) { var hint = 30 - lw if (hint < 0) hint = 0 if (hint > 40) hint = 40 header.leeway = hint } } // to be able to tell the model that we've seen the new transactions, we first // create a bool that shows the user is actually able to see this tab (part of ActivityTab). property bool tabVisible: root.active && root.visible Timer { id: markSeenTimer property var startDate: new Date() interval: 80000 onTriggered: portfolio.current.transactions.markSeen() running: listView.tabVisible onRunningChanged: if (running) startDate = new Date(); function speedup() { if (running) { // subtract at most 20s from the timeout stop() let elapsed = (new Date() - startDate); interval = Math.max(400, (interval - elapsed - 20000)) start() } } } Connections { target: portfolio function onCurrentChanged() { markSeenTimer.restart() // starting in Qt6.10 this is needed to work around // a crash in Qt. Changing the model while not at the top // would crash. So we move to the top before switching. // last known-crashing version: 6.10.2 listView.positionViewAtBeginning() listView.model = portfolio.current.transactions markSeenTimer.interval = 80000 } } // see comment in the onCurrentChanged on why this is done this way Component.onCompleted: model = portfolio.current.transactions Rectangle { id: backToTopButton width: 70 height: 70 radius: 30 anchors.right: parent.right anchors.rightMargin: 30 y: { var indexAtTopOfScreen = listView.indexAt(10, listView.contentY + 10) if (indexAtTopOfScreen === -1) { // just in case that's just a section-header indexAtTopOfScreen = listView.indexAt(10, listView.contentY + 80) } if (indexAtTopOfScreen > 3) return listView.height - height - 15 return height * -3 // out of screen. } color: "#66000000" border.width: 1.3 border.color: palette.button Image { id: upIcon source: "qrc:/back-arrow.svg" rotation: 90 width: 15 height: 20 anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: listView.positionViewAtIndex(0, ListView.Contain) } Behavior on y { NumberAnimation { easing.type: Easing.OutElastic duration: 500 } } } QQC2.ScrollBar.vertical: Flowee.ScrollThumb { id: thumb minimumSize: 0.05 visible: size < 0.9 preview: Rectangle { width: dateLabel.width + 20 height: dateLabel.height + 20 radius: 5 color: palette.light border.width: 1 border.color: palette.highlight Flowee.Label { id: dateLabel anchors.centerIn: parent color: palette.dark text: listView.model.dateForItem(thumb.position) } } } clip: true focus: true section.property: "grouping" section.labelPositioning: ViewSection.InlineLabels + ViewSection.CurrentLabelAtStart section.delegate: Item { height: label.height + 3 width: root.width Rectangle { color: palette.base anchors.fill: parent } Flowee.Label { id: label x: 10 font.bold: true font.pixelSize: mainWindow.font.pixelSize * 1.1 text: portfolio.current.transactions.groupingPeriod(section) } MouseArea { anchors.fill: parent } // eat all taps } delegate: Loader { width: root.width source: { if (model.isTransaction) return "./TransactionDelegate.qml" if (model.isUnseenRow) return "./UnseenDelegate.qml" if (model.isCollapsedRow) return "./HiddenItemsDelegate.qml" return "" } } displaced: Transition { NumberAnimation { properties: "y"; duration: 400 } } Keys.forwardTo: Flowee.ListViewKeyHandler { target: listView } } }