Warning, /utilities/keysmith/src/contents/ui/TOTPAccountEntryView.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  * SPDX-License-Identifier: GPL-3.0-or-later
0003  * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
0004  */
0005 
0006 import QtQuick
0007 import org.kde.kirigami as Kirigami
0008 
0009 AccountEntryViewBase {
0010     /*
0011      * WARNING: AccountEntryViewBase is a derivative of SwipeListItem and SwipeListem instances *must* be
0012      * called `listItem`. This took *way* too long to figure out. If you change it, things will break for example the
0013      * flood fill effect when pressing a list entry on Android.
0014      */
0015     id: listItem
0016 
0017     property real healthIndicator: 0
0018     property real interval: listItem.alive ? 1000 * listItem.account.timeStep : 0
0019 
0020     actions: [
0021         Kirigami.Action {
0022             icon.name: "documentinfo"
0023             text: i18nc("Button to show details of a single account", "Show details")
0024             enabled: listItem.alive
0025             onTriggered: {
0026                 listItem.actionTriggered();
0027                 applicationWindow().pageStack.pushDialogLayer(listItem.details, {}, {
0028                     width: Kirigami.Units.gridUnit * 20,
0029                 });
0030             }
0031         },
0032         Kirigami.Action {
0033             icon.name: "edit-delete"
0034             text: i18nc("Button for removal of a single account", "Delete account")
0035             enabled: listItem.alive
0036             onTriggered: {
0037                 listItem.actionTriggered();
0038                 listItem.sheet.open();
0039             }
0040         }
0041     ]
0042 
0043     /*
0044      * If the application is suspended the displayed state may be out-of-date by the time the application is woken from
0045      * suspend again. Use a property to monitor for this condition and recover when the application wakes: reset timers,
0046      * animations and recompute token in case it has lapsed.
0047      */
0048     property bool shouldBeActive: Qt.application.state === Qt.ApplicationActive
0049     onShouldBeActiveChanged: {
0050         if (listItem.alive && listItem.shouldBeActive) {
0051             timer.stop()
0052             timeoutIndicatorAnimation.stop();
0053 
0054             listItem.account.recompute();
0055 
0056             var phase = listItem.account.millisecondsLeftForToken();
0057             timer.interval = phase;
0058             listItem.healthIndicator = phase;
0059             timeoutIndicatorAnimation.duration = phase;
0060             timeoutIndicatorAnimation.from = phase;
0061 
0062             timer.restart();
0063             timeoutIndicatorAnimation.restart();
0064         }
0065     }
0066 
0067     contentItem: TokenEntryViewLabels {
0068         id: mainLayout
0069         accountName: account.name
0070         tokenValue: account.token
0071         labelColor: listItem.labelColor
0072 
0073         /*
0074          * For some reason the running NumberAnimation seems to trigger very sluggish QML UI when the window is resized.
0075          * This behaviour persists until the animation is 'reset', so work around by resetting the animation whenever this
0076          * could have occurred. The easiest proxy for detecting this is whenever the width of the content item changes.
0077          *
0078          * Note that this work-around triggers a lot of false positive 'hits' as well: hovering the cursor over the UI
0079          * also triggers a change on the width property.
0080          *
0081          * The particular sluggish behaviour of QML can be reproduced using the following steps:
0082          *
0083          *  - commenting out the signal handler
0084          *  - rebuilding the app and starting it
0085          *  - with some (multiple) accounts pre-defined, with at least one TOTP account
0086          *  - resize the app while a health indicator animation is running
0087          *  - hovering over account entries: observe how QML takes a while to 'catch' up with the cursor, to display the
0088          *    hover effect in the accounts list view.
0089          */
0090         onWidthChanged: {
0091             if (timeoutIndicatorAnimation.running) {
0092                 timeoutIndicatorAnimation.stop();
0093                 var phase = listItem.account.millisecondsLeftForToken();
0094 
0095                 listItem.healthIndicator = phase;
0096                 timeoutIndicatorAnimation.from = phase;
0097                 timeoutIndicatorAnimation.duration = phase;
0098                 timeoutIndicatorAnimation.restart();
0099             }
0100         }
0101 
0102         Rectangle {
0103             id: health
0104             // make the indicator sit flush with the bottom edge of the list item
0105             y: listItem.height - health.height - listItem.bottomPadding
0106             /*
0107              * Horizontal positioning of the rectangle relies on clippling of the list item. The idea is to make the health
0108              * indicator sit flush with the left border while maintaining soft rounded corners on the right (asymmetry).
0109              *
0110              * To achieve this simply add a dummy amount of width to the rectangle and compensate for it by offseting it
0111              * a corresponding amount further to the left; due to clipping the dummy amount will not be shown (but the
0112              * portion being clipped will contain the rounded corners on the left). The required length for the dummy part
0113              * depends on the corner radius of the rectangle.
0114              */
0115             x: - listItem.leftPadding - health.height
0116             width: listItem.alive && listItem.interval > 0 ? health.height + listItem.width * listItem.healthIndicator / listItem.interval : 0
0117             radius: health.height // right edge becomes a semi-circle
0118             /*
0119              * Height and opacity are a bit of a balancing act between good looking visuals with few accounts and avoiding
0120              * an overwhelming UI with many accounts (and therefore many running animations). Opacity is increased when
0121              * highlighted to get better contrast.
0122              */
0123             height: Kirigami.Units.devicePixelRatio * 6
0124             opacity: timeoutIndicatorAnimation.running ? listItem.highlightActive ? 0.6 : 0.4 : 0
0125             color: listItem.highlightActive ? listItem.labelColor : Kirigami.Theme.positiveTextColor
0126             NumberAnimation {
0127                 id: timeoutIndicatorAnimation
0128                 target: listItem
0129                 property: "healthIndicator"
0130                 from: timer.interval
0131                 to: 0
0132                 duration: timer.interval
0133                 running: listItem.alive
0134             }
0135             Timer {
0136                 id: timer
0137                 running: listItem.alive
0138                 interval: listItem.alive ? listItem.account.millisecondsLeftForToken() : 0
0139                 onTriggered: {
0140                     if (listItem.alive) {
0141                         timer.stop()
0142                         timeoutIndicatorAnimation.stop();
0143 
0144                         listItem.account.recompute();
0145 
0146                         var phase = listItem.account.millisecondsLeftForToken();
0147                         timer.interval = phase;
0148                         listItem.healthIndicator = phase;
0149                         timeoutIndicatorAnimation.duration = phase;
0150                         timeoutIndicatorAnimation.from = phase;
0151 
0152                         timer.restart();
0153                         timeoutIndicatorAnimation.restart();
0154                     }
0155                 }
0156             }
0157         }
0158     }
0159 }