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 }