/* * This file is part of the Flowee project * Copyright (C) 2023-2025 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 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); } } } 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() { item.forceActiveFocus(); 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 // 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; } } } } Rectangle { id: startQRButton width: height height: { let i = activityTabs.currentItem; if (i != 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); } } }