Verified Commit 28fcc48e authored by Carl Schwan's avatar Carl Schwan 🚴
Browse files

Revamp health certificate UI

* Split confidential details from bare minimum information required to show
* Make it easy to switch between Qr code and details with a Kirigami.NavigationTabBar
* When importing certificate, switch to it directly
parent 0ce104bc
......@@ -148,6 +148,7 @@ if (ANDROID)
view-barcode-qr
view-calendar-day
view-list-symbolic
view-list-details
view-refresh
view-statistics
zoom-in-symbolic
......
......@@ -4,32 +4,89 @@
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.1 as QQC2
import org.kde.kirigami 2.17 as Kirigami
import org.kde.prison 1.0 as Prison
import org.kde.khealthcertificate 1.0 as KHC
import org.kde.itinerary 1.0
Rectangle {
id: validationBg
property var certificate
Layout.fillWidth: true
implicitHeight: barcode.height + Kirigami.Units.largeSpacing * 4
QQC2.Page {
id: page
required property var certificate
Prison.Barcode {
anchors.centerIn: parent
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
header: ColumnLayout {
Kirigami.Heading {
Layout.margins: Kirigami.Units.largeSpacing
Layout.bottomMargin: 0
Layout.fillWidth: true
level: 2
wrapMode: Text.WordWrap
text: {
const name = certificate.name.toLowerCase().replace(/(^|\s)\S/g, (t) => { return t.toUpperCase() });
switch (certificate.type) {
case KHC.HealthCertificate.Vaccination:
return i18n('Vaccination certificate of %1', name);
case KHC.HealthCertificate.Test:
return i18n('Test certificate of %1', name);
case KHC.HealthCertificate.Recovery:
return i18n('Recovery certificate of %1', name);
default:
return '';
}
}
}
Kirigami.Heading {
Layout.margins: Kirigami.Units.largeSpacing
Layout.bottomMargin: 0
Layout.topMargin: 0
Layout.fillWidth: true
level: 3
wrapMode: Text.WordWrap
text: {
if (certificate.validationState) {
return i18n('Certificate invalid');
}
switch (certificate.type) {
case KHC.HealthCertificate.Vaccination:
switch (certificate.validationState) {
case KHC.HealthCertificate.Valid: return i18n('Fully vaccinated');
case KHC.HealthCertificate.Partial: return i18n('Partial vaccinated');
}
case KHC.HealthCertificate.Test:
case KHC.HealthCertificate.Valid: return i18n('Test valid');
case KHC.HealthCertificate.Recovery:
return i18n('Recovery certificate valid');
default:
return '';
}
}
}
}
contentItem: Prison.Barcode {
id: barcode
barcodeType: Prison.Barcode.QRCode
content: certificate.rawData
width: Math.max(Math.floor(validationBg.width / minimumWidth) * minimumWidth, minimumWidth)
height: width
anchors.centerIn: parent
}
footer: Item {
height: Kirigami.Units.gridUnit * 4
}
color: switch (certificate.validationState) {
case KHC.HealthCertificate.Valid: return Kirigami.Theme.positiveTextColor;
case KHC.HealthCertificate.Partial: return Kirigami.Theme.neutralTextColor;
case KHC.HealthCertificate.Invalid: return Kirigami.Theme.negativeTextColor;
default: return "transparent"
background: Rectangle {
opacity: 0.4
color: switch (certificate.validationState) {
case KHC.HealthCertificate.Valid: return Kirigami.Theme.positiveTextColor;
case KHC.HealthCertificate.Partial: return Kirigami.Theme.neutralTextColor;
case KHC.HealthCertificate.Invalid: return Kirigami.Theme.negativeTextColor;
default: return "transparent"
}
}
}
......@@ -4,29 +4,50 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.5
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.1 as QQC2
import org.kde.kirigami 2.17 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.khealthcertificate 1.0 as KHC
import org.kde.itinerary 1.0
import "." as App
Kirigami.ScrollablePage {
Kirigami.Page {
id: root
title: i18n("Health Certificates")
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
readonly property bool hasValidCertificate: {
if (certSelector.count === 0 || !certSelector.currentValue) {
return false;
}
switch (certSelector.currentValue.type) {
case KHC.HealthCertificate.Vaccination:
case KHC.HealthCertificate.Test:
case KHC.HealthCertificate.Recovery:
return true;
default:
return false;
}
}
actions {
main: Kirigami.Action {
icon.name: "view-barcode-qr"
text: i18n("Barcode Scan Mode")
onTriggered: scanModeController.toggle()
visible: certSelector.currentValue != undefined
visible: certSelector.currentValue !== undefined && swipeView.currentIndex === 0
checkable: true
checked: scanModeController.enabled
}
contextualActions: [
Kirigami.Action {
id: importFromClipboardAction
iconName: "edit-paste"
text: i18n("Import from Clipboard")
onTriggered: ApplicationController.importFromClipboard()
......@@ -59,24 +80,27 @@ Kirigami.ScrollablePage {
}
}
}
header: Item {
height: childrenRect.height + 2 * Kirigami.Units.largeSpacing
width: parent.width
header: ColumnLayout {
visible: certSelector.count > 0
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Kirigami.Units.largeSpacing
QQC2.ComboBox {
id: certSelector
Layout.fillWidth: true
model: HealthCertificateManager
valueRole: "certificate"
textRole: "display"
spacing: 0
QQC2.ComboBox {
id: certSelector
Layout.margins: Kirigami.Units.largeSpacing
Layout.fillWidth: true
model: HealthCertificateManager
valueRole: "certificate"
textRole: "display"
Connections {
target: HealthCertificateManager
function onNewCertificateLoaded(index) {
certSelector.currentIndex = index;
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
}
Component {
......@@ -97,40 +121,90 @@ Kirigami.ScrollablePage {
certificate: certSelector.currentValue
}
}
Component {
id: helpText
ColumnLayout {
visible: !hasValidCertificate
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.largeSpacing * 4)
spacing: Kirigami.Units.largeSpacing
Kirigami.Heading {
text: i18n('No health certificates found')
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
Layout.fillWidth: true
level: 2
}
QQC2.Label {
text: i18n("<p>You can import the following health certificates by scanning them with a barcode scanner app:<ul><li>European \"Digital Green Certificates\" for vaccinations, tests or recovery.</li><li>Indian vaccination certificates.</li></p>")
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: Kirigami.Units.gridUnit * 25
}
QQC2.Button {
action: importFromClipboardAction
Layout.alignment: Qt.AlignHCenter
}
}
Loader {
id: loader
sourceComponent: {
if (certSelector.count == 0 || !certSelector.currentValue)
return helpText;
switch (certSelector.currentValue.type) {
case KHC.HealthCertificate.Vaccination:
return vaccinationDetails;
case KHC.HealthCertificate.Test:
return testDetails;
case KHC.HealthCertificate.Recovery:
return recoveryDetails;
footer: Kirigami.NavigationTabBar {
visible: hasValidCertificate
actions: [
Kirigami.Action {
text: i18n('Certificate')
icon.name: 'view-barcode-qr'
onTriggered: swipeView.currentIndex = 0
checked: swipeView.currentIndex === 0
},
Kirigami.Action {
text: i18n('Detail')
icon.name: 'view-list-details'
onTriggered: swipeView.currentIndex = 1;
checked: swipeView.currentIndex === 1
}
return helpText;
]
}
QQC2.SwipeView {
id: swipeView
anchors.fill: parent
onCurrentIndexChanged: if (swipeView.currentIndex === 1) {
scanModeController.enabled = false;
}
BarcodeScanModeController {
id: scanModeController
page: root
App.HealthCertificateBarcode {
certificate: certSelector.currentValue
TapHandler {
onDoubleTapped: scanModeController.toggle()
}
implicitWidth: parent.width
}
Connections {
target: loader.item
function onScanModeToggled() {
scanModeController.toggle();
Kirigami.ScrollablePage {
padding: Kirigami.Units.largeSpacing
Loader {
id: loader
width: parent.width
sourceComponent: {
if (!hasValidCertificate) {
return undefined;
}
switch (certSelector.currentValue.type) {
case KHC.HealthCertificate.Vaccination:
return vaccinationDetails;
case KHC.HealthCertificate.Test:
return testDetails;
case KHC.HealthCertificate.Recovery:
return recoveryDetails;
default:
return undefined;
}
}
BarcodeScanModeController {
id: scanModeController
page: root
}
}
}
}
}
......@@ -15,22 +15,12 @@ import "." as App
ColumnLayout {
id: root
width: parent.width
property var certificate
/** Double tap on the barcode to request scan mode. */
signal scanModeToggled()
required property var certificate
function daysTo(d1, d2) {
return (d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24);
}
App.HealthCertificateBarcode {
certificate: root.certificate
TapHandler {
onDoubleTapped: root.scanModeToggled()
}
}
Kirigami.FormLayout {
Layout.fillWidth: true
......
......@@ -17,20 +17,10 @@ ColumnLayout {
width: parent.width
property var certificate
/** Double tap on the barcode to request scan mode. */
signal scanModeToggled()
function daysTo(d1, d2) {
return (d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24);
}
App.HealthCertificateBarcode {
certificate: root.certificate
TapHandler {
onDoubleTapped: root.scanModeToggled()
}
}
Kirigami.FormLayout {
Layout.fillWidth: true
......
......@@ -24,13 +24,6 @@ ColumnLayout {
return (d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24);
}
App.HealthCertificateBarcode {
certificate: root.certificate
TapHandler {
onDoubleTapped: root.scanModeToggled()
}
}
Kirigami.FormLayout {
Layout.fillWidth: true
......
......@@ -78,6 +78,7 @@ bool HealthCertificateManager::importCertificate(const QByteArray &rawData)
beginInsertRows({}, row, row);
m_certificates.insert(it, std::move(certData));
endInsertRows();
Q_EMIT newCertificateLoaded(row);
return true;
#else
return false;
......
......@@ -38,6 +38,9 @@ public:
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void newCertificateLoaded(int index);
private:
struct CertData {
QString name;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment