Files
pay/guis/mobile/ActivityTab.qml
T
tomFlowee 84221a3164 Persist 'last seen' bar index.
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.
2026-02-12 23:06:58 +01:00

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)
}
}
}