Files
pay/guis/mobile/MenuOverlay.qml
T
tomFlowee 38f969629e From Android 15 we support Insets
The default changed there and the application at full screen is actually
fully full screen. Meaning we need to make space for the statusbar and
menu.

This code fetches the relevant insets from Android and the GUI honers
them in a couple of choice widgets.
2025-08-31 19:28:51 +02:00

340 lines
12 KiB
QML

/*
* This file is part of the Flowee project
* Copyright (C) 2022-2025 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 QtQuick.Layouts
import "../Flowee" as Flowee
import Flowee.org.pay;
Item {
id: root
property bool open: false
onOpenChanged: if (!open) baseArea.openAccounts = false; // close the accounts when the menu is closed
Rectangle {
anchors.fill: parent
opacity: {
if (!root.open)
return 0;
// we become 50% opaque when the menuArea is fully open (x == 0)
return (menuArea.x + 250) / 500;
}
color: "black"
}
Rectangle {
id: menuArea
color: palette.window
width: 300
height: parent.height
x: root.open ? 0 : 0 - width -3
clip: true
Item {
id: contentArea // avoid the screen insets
y: Pay.screenInsets.y
x: Pay.screenInsets.x
width: parent.width - x
height: parent.height - y - Pay.screenInsets.height
Rectangle {
id: baseArea
width: parent.width
height: {
var h = logo.height + 20;
// if its opened
if (openAccounts)
h = h + extraOptions.height + 10
// but we just don't show the accounts at all if
// this is the initial (empty) wallet.
if (!isLoading && portfolio.accounts.length > 1)
h = h+ Math.max(currentAccountName.height, 12) + 10
return h;
}
color: Qt.lighter(palette.window)
property bool openAccounts: false
clip: true
Rectangle {
id: logo
width: 70
height: 70
x: 5
y: 5
radius: 35
color: mainWindow.floweeBlue
Item {
// clip the logo only, ignore the text part
width: 50
height: 50
clip: true
x: 13
y: 16
Image {
source: "qrc:/FloweePay-light.svg"
// ratio: 449 / 77
width: height / 77 * 449
height: 50
}
}
}
Image {
width: 25
height: 25
anchors.right: parent.right
anchors.rightMargin: 10
y: 10
source: Pay.useDarkSkin ? "qrc:/maslenica.svg" : "qrc:/moon.svg"
MouseArea {
anchors.fill: parent
anchors.margins: -10
property date colorChanged: new Date(2000)
onClicked: {
var now = new Date();
if (now - colorChanged < 3 * 1000)
return;
Pay.skinFollowsPlatform = false;
Pay.useDarkSkin = !Pay.useDarkSkin;
colorChanged = now;
}
}
}
Flowee.Label {
id: currentAccountName
x: 10
y: logo.height + 20
text: {
if (mainWindow.isLoading)
return ""
return portfolio.current.name
}
}
Image {
id: openButton
source: Pay.useDarkSkin ? "qrc:/smallArrow-light.svg" : "qrc:/smallArrow.svg"
rotation: baseArea.openAccounts ? 180 : 0
Behavior on rotation { NumberAnimation {} }
anchors.right: parent.right
anchors.rightMargin: 10
anchors.bottom: currentAccountName.bottom
anchors.bottomMargin: 8
width: 20
height: 7
}
MouseArea {
anchors.top: currentAccountName.top
anchors.bottom: openButton.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: -10
onClicked: baseArea.openAccounts = !baseArea.openAccounts
}
ColumnLayout {
id: extraOptions
width: baseArea.width - 20
y: logo.height + 20 + (currentAccountName.visible ? currentAccountName.height + 10 : 0)
x: 10
Repeater { // portfolio holds all our accounts
width: parent.width
model: mainWindow.isLoading ? 0 : portfolio.accounts
TextButton {
text: modelData.name
visible: portfolio.current !== modelData
onClicked: {
portfolio.current = modelData
baseArea.openAccounts = false
}
}
}
Loader {
width: parent.width
active: !isLoading && portfolio.accounts.length > 1
sourceComponent: addWalletRow
}
}
Behavior on height { NumberAnimation { } }
}
Flickable {
anchors.top: baseArea.bottom
anchors.topMargin: 10
anchors.bottom: versionLabel.top
contentHeight: contentLayout.height
width: parent.width - 20
clip: true
boundsBehavior: Flickable.StopAtBounds // don't show this is a flickable if there is no space issues
x: 10
ColumnLayout {
id: contentLayout
width: parent.width
Repeater {
model: MenuModel
TextButton {
text: model.name
pageButton: true
buttonId: model.id
onClicked: {
var target = model.target
if (target !== "") {
thePile.push(model.target)
root.open = false;
}
}
}
}
Loader {
width: parent.width
active: isLoading || portfolio.accounts.length <= 1
sourceComponent: addWalletRow
}
}
}
Flowee.Label {
id: versionLabel
x: 10
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
text: "Flowee Pay (mobile) v" + Application.version
font.pointSize: mainWindow.font.pointSize * 0.9
font.bold: false
wrapMode: Text.Wrap
width: parent.width - 20
}
}
Behavior on x { NumberAnimation { duration: 100 } }
property bool opened: false
onXChanged: {
if (!root.open)
opened = false;
else if (x === 0)
opened = true;
// close on user drag to the left
if (opened && x < -50)
root.open = false
}
// gesture (swipe right) to close menu
DragHandler {
id: dragHandler
enabled: root.open
yAxis.enabled: false
xAxis.minimum: -200
xAxis.maximum: 0
onActiveChanged: {
// should the user abort the swipe left, restore
// the original binding
if (!active && root.open)
menuArea.x = root.open ? 0 : 0 - width -3
}
}
}
// allow close by clicking next to the menu
MouseArea {
width: parent.width - menuArea.width
height: parent.height
anchors.right: parent.right
enabled: root.open
onClicked: root.open = false;
}
Item {
id: menuSwipy
width: 50
height: parent.height / 3
anchors.bottom: parent.bottom
onXChanged: {
// moving this drag area makes the menu slowly open.
let SwipeDistance = dragOpenHandler.xAxis.maximum
let progress = x / SwipeDistance;
let menuX = 0;
if (progress < 0.2) // threshold for movement
return;
if (progress < 0.39) { // first 50% movement
menuX = Math.pow((progress - 0.1) * 10, 2) * SwipeDistance / 10;
}
else {
// progress between 0.4 and 1.0
// this movement goes linear instead.
menuX = 10 + (progress - 0.4) * 2 * (menuArea.width / 2) + menuArea.width / 2;
}
menuArea.x = Math.min(0, 0 - menuArea.width + menuX + 10);
}
DragHandler {
id: dragOpenHandler
enabled: root.open === false && thePile.depth === 1 && Pay.appProtection !== FloweePay.AppPassword
yAxis.enabled: false // the anchors of parent do that too ¯\_(ツ)_/¯
xAxis.minimum: 0
xAxis.maximum: mainWindow.width / 2
acceptedDevices: PointerDevice.TouchScreen | PointerDevice.Stylus
onActiveChanged: {
if (!active) {
if (menuArea.x > -30)
root.open = true;
menuSwipy.x = 0; // reset
// restore the original binding
menuArea.x = root.open ? 0 : 0 - width -3
}
}
}
}
Component {
id: addWalletRow
Item {
height: addWalletButton.height
Rectangle {
id: horizontalBar
width: 13
height: 2
x: 2
color: palette.mid
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: verticalBar
width: 2
height: 13
anchors.horizontalCenter: horizontalBar.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: palette.mid
}
TextButton {
id: addWalletButton
text: qsTr("Add Wallet")
pageButton: true
anchors.left: horizontalBar.right
anchors.leftMargin: 6
anchors.right: parent.right
onClicked: {
thePile.push("./NewAccount.qml")
root.open = false
baseArea.openAccounts = false
}
}
}
}
}