Files
pay/guis/mobile/ActivityTab.qml
T

294 lines
10 KiB
QML
Raw Permalink Normal View History

2025-06-24 22:19:37 +02:00
/*
* This file is part of the Flowee project
2026-02-12 23:06:58 +01:00
* Copyright (C) 2023-2026 Tom Zander <tom@flowee.org>
2025-06-24 22:19:37 +02:00
*
* 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
2026-02-04 15:02:32 +01:00
import Flowee.org.pay
2025-06-24 22:19:37 +02:00
Item {
2026-02-04 15:02:32 +01:00
property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg")
2025-06-24 22:19:37 +02:00
property string title: qsTr("Home")
property bool showFilterIcon: tabBar.currentIndex === 0
property bool itIsChristmas: {
2026-02-04 15:02:32 +01:00
var today = new Date()
2025-06-24 22:19:37 +02:00
return today.getMonth() == 11 && today.getDate() >= 24 && today.getDate() <= 31
}
2025-06-25 00:17:25 +02:00
Rectangle {
2025-07-12 16:56:08 +02:00
id: headerBg
2025-06-25 00:17:25 +02:00
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: tabBar.bottom
2025-07-12 16:56:08 +02:00
anchors.bottomMargin: -6
2025-06-25 00:17:25 +02:00
color: palette.window
2025-07-12 16:56:08 +02:00
property var headerGradient: Gradient {
2025-08-03 14:01:38 +02:00
GradientStop { position: 0.93; color: headerBg.color }
2025-07-12 16:56:08 +02:00
GradientStop { position: 1; color: {
2026-02-04 15:02:32 +01:00
let c = headerBg.color
return Qt.rgba(c.r, c.g, c.b, 0)
2025-07-12 16:56:08 +02:00
}
}
}
2025-08-03 14:01:38 +02:00
gradient: tabBar.visible ? undefined : headerGradient
2025-06-25 00:17:25 +02:00
}
2025-06-24 22:19:37 +02:00
Column {
id: header
2026-02-04 15:02:32 +01:00
property int leeway: 30
2025-06-26 00:02:57 +02:00
Behavior on leeway { NumberAnimation {} }
2025-06-24 22:19:37 +02:00
width: parent.width
2025-11-01 21:30:30 +01:00
y: {
if (!accountSyncState.hasProgressbar && accountSyncState.implicitHeight > 0)
2026-02-04 15:02:32 +01:00
return leeway - accountSyncState.initialHeight / 4 - 8
return leeway
2025-11-01 21:30:30 +01:00
}
spacing: {
if (!accountSyncState.hasProgressbar && accountSyncState.implicitHeight > 0)
2026-02-04 15:02:32 +01:00
return accountSyncState.initialHeight / -4 + 4
2025-11-01 21:30:30 +01:00
return 0
}
2025-06-24 22:19:37 +02:00
Flowee.BitcoinAmountLabel {
id: bchPriceWidget
opacity: Pay.hideBalance ? 0.2 : 1
fontPixelSize: 30
anchors.horizontalCenter: parent.horizontalCenter
value: {
if (Pay.hideBalance)
2026-02-04 15:02:32 +01:00
return 88888888
2025-06-24 22:19:37 +02:00
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 {
2025-11-01 21:30:30 +01:00
id: accountSyncState
2025-06-24 22:19:37 +02:00
account: portfolio.current
width: parent.width
2025-11-01 21:30:30 +01:00
property int initialHeight: -1
Component.onCompleted: initialHeight = implicitHeight
2025-06-24 22:19:37 +02:00
}
2025-06-25 00:17:25 +02:00
Item { width: 1; height: parent.leeway + 1 } // spacer
2025-06-24 22:19:37 +02:00
}
ListView {
id: tabBar
orientation: Qt.Horizontal
width: parent.width
height: {
2025-07-12 16:05:38 +02:00
if (!visible || count <= 1 || currentItem === null)
2026-02-04 15:02:32 +01:00
return 0
return currentItem.height
2025-06-24 22:19:37 +02:00
}
anchors.top: header.bottom
property bool havePlannedTab: Pay.haveRepeatPayments
visible: havePlannedTab
2025-06-24 22:19:37 +02:00
function selectTab(index) {
// fast move
2026-02-04 15:02:32 +01:00
activityTabs.currentIndex = index
activityTabs.positionViewAtIndex(index, ListView.Beginning)
// slow scroll
2026-02-04 15:02:32 +01:00
currentIndex = index
}
2025-06-24 22:19:37 +02:00
clip: true
boundsBehavior: Flickable.StopAtBounds
currentIndex: activityTabs.currentIndex
model: ListModel {
id: tabsModel
2025-06-24 22:19:37 +02:00
ListElement {
2025-07-08 20:54:56 +02:00
title: qsTr("Activity")
2025-06-24 22:19:37 +02:00
qml: "AccountHistory.qml"
}
}
onHavePlannedTabChanged: {
if (havePlannedTab && tabsModel.count < 2) {
2026-02-04 15:02:32 +01:00
tabsModel.append({ title: qsTr("Planned"), qml: "PlannedPayments.qml" })
2025-06-24 22:19:37 +02:00
}
}
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
2025-06-25 00:17:25 +02:00
color: palette.windowText
2025-06-24 22:19:37 +02:00
text: model.title
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
2026-02-04 15:02:32 +01:00
onClicked: tabBar.selectTab(index)
2025-06-24 22:19:37 +02:00
}
}
}
2026-02-05 17:49:52 +01:00
Rectangle {
color: palette.base // background is dark.
anchors.fill: activityTabs
}
2025-06-24 22:19:37 +02:00
ListView {
id: activityTabs
anchors.top: tabBar.bottom
2025-07-12 16:56:08 +02:00
anchors.topMargin: tabBar.visible ? 0 : 6
2025-06-24 22:19:37 +02:00
anchors.bottom: parent.bottom
width: parent.width
model: tabBar.model
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
2026-02-04 15:02:32 +01:00
onContentXChanged: currentIndex = Math.round(contentX / width)
2025-06-24 22:19:37 +02:00
boundsBehavior: Flickable.StopAtBounds
2026-02-04 15:02:32 +01:00
onCurrentItemChanged: currentItem.makeActive()
2025-06-25 12:29:08 +02:00
onCurrentIndexChanged: tabBar.currentIndex = currentIndex
2025-06-24 22:19:37 +02:00
delegate: Loader {
2025-06-25 00:17:25 +02:00
id: delegateRoot
2025-06-24 22:19:37 +02:00
width: activityTabs.width
height: activityTabs.height
source: model.qml
2025-06-25 00:17:25 +02:00
function makeActive() {
2026-02-12 23:06:58 +01:00
if (item != null) {
item.forceActiveFocus()
if (typeof(item.leewayHint) == "number")
leewayHandler.target = item
}
2025-06-25 00:17:25 +02:00
}
// 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
2026-02-12 23:06:58 +01:00
target: null
enabled: target != null
2025-06-25 00:17:25 +02:00
// notice that this gives warnings before the item is loaded,
// but it works, so just ignore.
function onLeewayHintChanged() {
var hint = target.leewayHint
2026-02-04 15:02:32 +01:00
if (hint < 0) hint = 0
if (hint > 40) hint = 40
header.leeway = hint
2025-06-25 00:17:25 +02:00
}
}
2026-02-12 23:06:58 +01:00
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
}
}
2025-06-24 22:19:37 +02:00
}
}
Rectangle {
id: startQRButton
width: height
height: {
2026-02-04 15:02:32 +01:00
let i = activityTabs.currentItem
2026-02-12 23:06:58 +01:00
if (i != null && i.item !== null) { // at creation this is the case.
2025-06-24 22:19:37 +02:00
let hide = i.item.hideQRScanButton
if (typeof(hide) === "boolean" && hide)
2026-02-04 15:02:32 +01:00
return 0
2025-06-24 22:19:37 +02:00
}
2026-02-04 15:02:32 +01:00
return 70
2025-06-24 22:19:37 +02:00
}
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
2025-06-25 12:29:08 +02:00
width: 40
height: 40
2025-06-24 22:19:37 +02:00
}
MouseArea {
anchors.fill: parent
onClicked: thePile.push("ScanQRPage.qml")
}
Behavior on height { NumberAnimation { } }
}
2025-06-25 00:17:25 +02:00
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) {
2026-02-04 15:02:32 +01:00
event.accepted = true
tabBar.selectTab(0)
2025-06-25 00:17:25 +02:00
}
}
}
// 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)
2026-02-04 15:02:32 +01:00
tabBar.selectTab(1)
}
}
2025-06-24 22:19:37 +02:00
}