84221a3164
This makes the "new transactions above this line" concept more coherent. We now save the last known transaction in the model, which is only loaded in the GUI version of Pay. Then if new transactions are found (or created) in the background runner then the next time we start Pay, they will be marked as such. This also adds some logic to the UI to detect that the history is actually the visible component right now, and if it is then we start an 80 second timer that, after expiring, will reset the last seen to the most recent transaction.
294 lines
10 KiB
QML
294 lines
10 KiB
QML
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2023-2026 Tom Zander <tom@flowee.org>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
import QtQuick
|
|
import QtQuick.Controls.Basic as QQC2
|
|
import "../Flowee" as Flowee
|
|
import "../Utils.js" as Utils
|
|
import Flowee.org.pay
|
|
|
|
Item {
|
|
property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg")
|
|
property string title: qsTr("Home")
|
|
property bool showFilterIcon: tabBar.currentIndex === 0
|
|
|
|
property bool itIsChristmas: {
|
|
var today = new Date()
|
|
return today.getMonth() == 11 && today.getDate() >= 24 && today.getDate() <= 31
|
|
}
|
|
|
|
Rectangle {
|
|
id: headerBg
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.bottom: tabBar.bottom
|
|
anchors.bottomMargin: -6
|
|
color: palette.window
|
|
property var headerGradient: Gradient {
|
|
GradientStop { position: 0.93; color: headerBg.color }
|
|
GradientStop { position: 1; color: {
|
|
let c = headerBg.color
|
|
return Qt.rgba(c.r, c.g, c.b, 0)
|
|
}
|
|
}
|
|
}
|
|
gradient: tabBar.visible ? undefined : headerGradient
|
|
}
|
|
|
|
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: tabBar
|
|
orientation: Qt.Horizontal
|
|
width: parent.width
|
|
height: {
|
|
if (!visible || count <= 1 || currentItem === null)
|
|
return 0
|
|
return currentItem.height
|
|
}
|
|
anchors.top: header.bottom
|
|
property bool havePlannedTab: Pay.haveRepeatPayments
|
|
visible: havePlannedTab
|
|
|
|
function selectTab(index) {
|
|
// fast move
|
|
activityTabs.currentIndex = index
|
|
activityTabs.positionViewAtIndex(index, ListView.Beginning)
|
|
// slow scroll
|
|
currentIndex = index
|
|
}
|
|
|
|
clip: true
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
currentIndex: activityTabs.currentIndex
|
|
model: ListModel {
|
|
id: tabsModel
|
|
ListElement {
|
|
title: qsTr("Activity")
|
|
qml: "AccountHistory.qml"
|
|
}
|
|
}
|
|
onHavePlannedTabChanged: {
|
|
if (havePlannedTab && tabsModel.count < 2) {
|
|
tabsModel.append({ title: qsTr("Planned"), qml: "PlannedPayments.qml" })
|
|
}
|
|
}
|
|
|
|
delegate: Item {
|
|
width: Math.max(tabName.width, 120)
|
|
height: tabName.height + 20
|
|
|
|
Rectangle {
|
|
x: 5
|
|
height: 4
|
|
width: parent.width - 10
|
|
color: palette.highlight
|
|
visible: index === tabBar.currentIndex
|
|
anchors.bottom: parent.bottom
|
|
}
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: palette.highlight
|
|
visible: index === tabBar.currentIndex
|
|
opacity: 0.15
|
|
}
|
|
|
|
Text {
|
|
id: tabName
|
|
color: palette.windowText
|
|
text: model.title
|
|
anchors.centerIn: parent
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: tabBar.selectTab(index)
|
|
}
|
|
}
|
|
}
|
|
Rectangle {
|
|
color: palette.base // background is dark.
|
|
anchors.fill: activityTabs
|
|
}
|
|
ListView {
|
|
id: activityTabs
|
|
anchors.top: tabBar.bottom
|
|
anchors.topMargin: tabBar.visible ? 0 : 6
|
|
anchors.bottom: parent.bottom
|
|
width: parent.width
|
|
model: tabBar.model
|
|
|
|
orientation: ListView.Horizontal
|
|
snapMode: ListView.SnapOneItem
|
|
onContentXChanged: currentIndex = Math.round(contentX / width)
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
onCurrentItemChanged: currentItem.makeActive()
|
|
onCurrentIndexChanged: tabBar.currentIndex = currentIndex
|
|
delegate: Loader {
|
|
id: delegateRoot
|
|
width: activityTabs.width
|
|
height: activityTabs.height
|
|
source: model.qml
|
|
function makeActive() {
|
|
if (item != null) {
|
|
item.forceActiveFocus()
|
|
if (typeof(item.leewayHint) == "number")
|
|
leewayHandler.target = item
|
|
}
|
|
}
|
|
// Tabs that choose to do so can add the leewayHint property
|
|
// in order to make the spacing around the price compress as the
|
|
// user moves the list content.
|
|
Connections {
|
|
id: leewayHandler
|
|
target: null
|
|
enabled: target != null
|
|
// notice that this gives warnings before the item is loaded,
|
|
// but it works, so just ignore.
|
|
function onLeewayHintChanged() {
|
|
var hint = target.leewayHint
|
|
if (hint < 0) hint = 0
|
|
if (hint > 40) hint = 40
|
|
header.leeway = hint
|
|
}
|
|
}
|
|
Connections {
|
|
target: activityTabs
|
|
function onCurrentIndexChanged() {
|
|
// let a tab know if it is currently active.
|
|
// as the user can slide between them, we can't use visible and tabs have
|
|
// a right to know if they are actually visible.
|
|
if (item != null && typeof(item.active) != "undefined")
|
|
item.active = index == activityTabs.currentIndex
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Rectangle {
|
|
id: startQRButton
|
|
width: height
|
|
height: {
|
|
let i = activityTabs.currentItem
|
|
if (i != null && i.item !== null) { // at creation this is the case.
|
|
let hide = i.item.hideQRScanButton
|
|
if (typeof(hide) === "boolean" && hide)
|
|
return 0
|
|
}
|
|
return 70
|
|
}
|
|
radius: 35
|
|
clip: true
|
|
x: parent.width - width - 30 - (70 / 2 - width / 2) // almost right, but keep this centered around the max width
|
|
y: parent.height - height - 15 - (70 / 2 - height / 2)
|
|
color: mainWindow.floweeBlue
|
|
|
|
Image {
|
|
source: "qrc:/qr-code-scan-light.svg"
|
|
anchors.centerIn: parent
|
|
width: 40
|
|
height: 40
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: thePile.push("ScanQRPage.qml")
|
|
}
|
|
Behavior on height { NumberAnimation { } }
|
|
}
|
|
|
|
Keys.onPressed: (event)=> {
|
|
if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) {
|
|
// when we are on another tab, we can go to 'main' on back.
|
|
if (activityTabs.currentIndex !== 0) {
|
|
event.accepted = true
|
|
tabBar.selectTab(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
// should the app be started with the intent to open the "Planned Payment" screen,
|
|
// we handle that here.
|
|
Connections {
|
|
target: intent
|
|
function onGenericIntentChanged() { // it only ever changes at most once
|
|
if (intent.genericIntent === Intent.OpenPlannedPaymentScreen)
|
|
tabBar.selectTab(1)
|
|
}
|
|
}
|
|
}
|