diff --git a/look-and-feel/contents/lockscreen/Keypad.qml b/look-and-feel/contents/lockscreen/Keypad.qml index 5267e22e..6364f6ea 100644 --- a/look-and-feel/contents/lockscreen/Keypad.qml +++ b/look-and-feel/contents/lockscreen/Keypad.qml @@ -1,7 +1,7 @@ /* -SPDX-FileCopyrightText: 2020-2021 Devin Lin - -SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2020-2022 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later */ import QtQuick 2.12 @@ -19,19 +19,20 @@ import org.kde.kirigami 2.12 as Kirigami Rectangle { id: keypadRoot + required property var lockScreenState + // 0 - keypad is not shown, 1 - keypad is shown property double swipeProgress // slightly translucent background, for key contrast color: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.backgroundColor, {"alpha": 0.9*255}) - property string pinLabel: qsTr("Enter PIN") // colour calculations - property color buttonColor: Qt.lighter(PlasmaCore.Theme.backgroundColor, 1.3) - property color buttonPressedColor: Qt.darker(PlasmaCore.Theme.backgroundColor, 1.08) - property color buttonTextColor: PlasmaCore.Theme.textColor - property color dropShadowColor: Qt.darker(PlasmaCore.Theme.backgroundColor, 1.2) - property color headerBackgroundColor: Qt.lighter(PlasmaCore.Theme.backgroundColor, 1.3) + readonly property color buttonColor: Qt.lighter(PlasmaCore.Theme.backgroundColor, 1.3) + readonly property color buttonPressedColor: Qt.darker(PlasmaCore.Theme.backgroundColor, 1.08) + readonly property color buttonTextColor: PlasmaCore.Theme.textColor + readonly property color dropShadowColor: Qt.darker(PlasmaCore.Theme.backgroundColor, 1.2) + readonly property color headerBackgroundColor: Qt.lighter(PlasmaCore.Theme.backgroundColor, 1.3) opacity: Math.sin((Math.PI / 2) * swipeProgress + 1.5 * Math.PI) + 1 @@ -50,31 +51,6 @@ Rectangle { } } - signal passwordChanged() - - function reset() { - passwordBar.reset(); - } - - Connections { - target: authenticator - function onSucceeded() { - passwordBar.pinLabel = qsTr("Logging in..."); - passwordBar.waitingForAuth = false; - } - function onFailed() { - root.password = ""; - passwordBar.pinLabel = qsTr("Wrong PIN"); - passwordBar.waitingForAuth = false; - } - function onGraceLockedChanged() { - // try authenticating if it was waiting for grace lock to stop and it has stopped - if (!authenticator.graceLocked && passwordBar.waitingForAuth) { - authenticator.tryUnlock(root.password); - } - } - } - // listen for keyboard events Keys.onPressed: { if (event.modifiers === Qt.NoModifier) { @@ -107,30 +83,25 @@ Rectangle { // pin display and bar PasswordBar { id: passwordBar + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + color: keypadRoot.headerBackgroundColor opacity: (Math.sin(2*((Math.PI / 2) * keypadRoot.swipeProgress + 1.5 * Math.PI)) + 1) + + lockScreenState: keypadRoot.lockScreenState keypadOpen: swipeProgress === 1 - password: root.password previewCharIndex: -2 - pinLabel: qsTr("Enter PIN") - onPasswordChanged: keypadRoot.passwordChanged() - - onChangePassword: root.password = password - Binding { - target: passwordBar - property: "password" - value: root.password - } } // actual number keys ColumnLayout { visible: opacity > 0 opacity: passwordBar.isPinMode ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration diff --git a/look-and-feel/contents/lockscreen/LockScreen.qml b/look-and-feel/contents/lockscreen/LockScreen.qml index 84f802d4..798a477c 100644 --- a/look-and-feel/contents/lockscreen/LockScreen.qml +++ b/look-and-feel/contents/lockscreen/LockScreen.qml @@ -6,12 +6,14 @@ */ import QtQuick 2.12 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.notificationmanager 1.1 as Notifications +import org.kde.kirigami 2.12 as Kirigami + /** * Lockscreen component that is loaded after the device is locked. * @@ -20,24 +22,17 @@ import org.kde.notificationmanager 1.1 as Notifications PlasmaCore.ColorScope { id: root - property string password + property var lockScreenState: LockScreenState {} + property var notifModel: Notifications.WatchedNotificationsModel {} property bool isWidescreen: root.height < root.width * 0.75 property bool notificationsShown: false readonly property bool drawerOpen: flickable.openFactor >= 1 - function askPassword() { - flickable.goToOpenPosition(); - } - colorGroup: PlasmaCore.Theme.ComplementaryColorGroup anchors.fill: parent - Notifications.WatchedNotificationsModel { - id: notifModel - } - // wallpaper blur Loader { anchors.fill: parent @@ -55,9 +50,18 @@ PlasmaCore.ColorScope { anchors.fill: parent openFactor: flickable.openFactor - notificationsModel: notifModel + notificationsModel: root.notifModel onPasswordRequested: root.askPassword() } + + Connections { + target: root.lockScreenState + + // ensure keypad is opened when password is updated (ex. keyboard) + function onPasswordChanged() { + flickable.goToOpenPosition() + } + } FlickContainer { id: flickable @@ -67,11 +71,13 @@ PlasmaCore.ColorScope { keypadHeight: PlasmaCore.Units.gridUnit * 20 + // go to closed position when loaded Component.onCompleted: { flickable.position = 0; flickable.goToClosePosition(); } + // update position, and cap it at the keypad height onPositionChanged: { if (position > keypadHeight) { position = keypadHeight; @@ -94,10 +100,11 @@ PlasmaCore.ColorScope { fullHeight: root.height - notificationsModel: notifModel + lockScreenState: root.lockScreenState + notificationsModel: root.notifModel onNotificationsShownChanged: root.notificationsShown = notificationsShown - onPasswordRequested: root.askPassword() + onPasswordRequested: flickable.goToOpenPosition() anchors.top: parent.top anchors.bottom: scrollUpIconLoader.top @@ -110,14 +117,16 @@ PlasmaCore.ColorScope { LockScreenWideScreenContent { id: tabletComponent + visible: isWidescreen active: visible opacity: 1 - flickable.openFactor - notificationsModel: notifModel + lockScreenState: root.lockScreenState + notificationsModel: root.notifModel onNotificationsShownChanged: root.notificationsShown = notificationsShown - onPasswordRequested: root.askPassword() + onPasswordRequested: flickable.goToOpenPosition() anchors.topMargin: headerBar.statusBarHeight anchors.top: parent.top @@ -159,11 +168,31 @@ PlasmaCore.ColorScope { sourceComponent: ColumnLayout { transform: Translate { y: flickable.keypadHeight - flickable.position } - spacing: PlasmaCore.Units.gridUnit + spacing: 0 + + // info notification text + Label { + Layout.fillWidth: true + Layout.rightMargin: Kirigami.Units.largeSpacing + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.bottomMargin: PlasmaCore.Units.smallSpacing * 2 + font.pointSize: 9 + + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: root.lockScreenState.info + opacity: (root.lockScreenState.info.length === 0 || flickable.openFactor < 1) ? 0 : 1 + color: 'white' + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + } // scroll down icon PlasmaCore.IconItem { Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: PlasmaCore.Units.gridUnit implicitWidth: PlasmaCore.Units.iconSizes.smallMedium implicitHeight: PlasmaCore.Units.iconSizes.smallMedium colorGroup: PlasmaCore.Theme.ComplementaryColorGroup @@ -174,10 +203,10 @@ PlasmaCore.ColorScope { Keypad { id: keypad Layout.fillWidth: true - focus: true + + lockScreenState: root.lockScreenState swipeProgress: flickable.openFactor - onPasswordChanged: flickable.goToOpenPosition() } } } diff --git a/look-and-feel/contents/lockscreen/LockScreenNarrowContent.qml b/look-and-feel/contents/lockscreen/LockScreenNarrowContent.qml index 35d4ebe7..5e312c4a 100644 --- a/look-and-feel/contents/lockscreen/LockScreenNarrowContent.qml +++ b/look-and-feel/contents/lockscreen/LockScreenNarrowContent.qml @@ -15,6 +15,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell Loader { id: root + required property var lockScreenState property var notificationsModel: [] property bool notificationsShown: false @@ -67,6 +68,7 @@ Loader { NotificationsComponent { id: notificationComponent + lockScreenState: root.lockScreenState notificationsModel: root.notificationsModel Layout.fillHeight: true diff --git a/look-and-feel/contents/lockscreen/LockScreenState.qml b/look-and-feel/contents/lockscreen/LockScreenState.qml new file mode 100644 index 00000000..0edc9047 --- /dev/null +++ b/look-and-feel/contents/lockscreen/LockScreenState.qml @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2022 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQml 2.15 +import QtQuick 2.15 + +QtObject { + id: root + + // current password being typed + property string password: "" + + // whether waiting for authentication after trying password + property bool waitingForAuth: false + + property string info: "" + + signal reset() + signal unlockSucceeded() + signal unlockFailed() + + function tryPassword() { + if (root.password !== '') { // prevent typing lock when password is empty + waitingForAuth = true; + } + authenticator.tryUnlock(); + } + + function resetPassword() { + password = ""; + root.reset(); + } + + property var connections: Connections { + target: authenticator + + function onSucceeded() { + console.log('login succeeded'); + root.waitingForAuth = false; + root.unlockSucceeded(); + Qt.quit(); + } + + function onFailed() { + console.log('login failed'); + root.waitingForAuth = false; + root.password = ""; + root.unlockFailed(); + } + + function onInfoMessage(msg) { + console.log('info: ' + msg); + root.info += msg + " "; + } + + // TODO + function onErrorMessage(msg) { + console.log('error: ' + msg); + } + + // TODO + function onPrompt(msg) { + console.log('prompt: ' + msg); + } + + function onPromptForSecret(msg) { + console.log('prompt secret: ' + msg); + authenticator.respond(root.password); + authenticator.tryUnlock(); + } + + } +} diff --git a/look-and-feel/contents/lockscreen/LockScreenWideScreenContent.qml b/look-and-feel/contents/lockscreen/LockScreenWideScreenContent.qml index c00272ac..883385b4 100644 --- a/look-and-feel/contents/lockscreen/LockScreenWideScreenContent.qml +++ b/look-and-feel/contents/lockscreen/LockScreenWideScreenContent.qml @@ -15,6 +15,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell Loader { id: root + required property var lockScreenState property var notificationsModel: [] property bool notificationsShown: false @@ -65,6 +66,7 @@ Loader { NotificationsComponent { id: notificationComponent + lockScreenState: root.lockScreenState notificationsModel: root.notificationsModel Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter diff --git a/look-and-feel/contents/lockscreen/NotificationsComponent.qml b/look-and-feel/contents/lockscreen/NotificationsComponent.qml index 6190bf6e..93c8d20e 100644 --- a/look-and-feel/contents/lockscreen/NotificationsComponent.qml +++ b/look-and-feel/contents/lockscreen/NotificationsComponent.qml @@ -14,8 +14,8 @@ import org.kde.notificationmanager 1.1 as Notifications import org.kde.plasma.private.mobileshell 1.0 as MobileShell Rectangle { - id: rect - + id: root + required property var lockScreenState property var notificationsModel: [] readonly property bool notificationsShown: notificationsList.hasNotifications @@ -30,35 +30,37 @@ Rectangle { color: "transparent" clip: true - PlasmaCore.ColorScope { - anchors.fill: parent - anchors.topMargin: rect.topMargin - anchors.bottomMargin: rect.bottomMargin - anchors.leftMargin: rect.leftMargin - anchors.rightMargin: rect.rightMargin - colorGroup: PlasmaCore.Theme.NormalColorGroup + Connections { + target: lockScreenState - Connections { - target: authenticator - function onSucceeded() { - // run pending action if successfully unlocked - if (notificationsList.requestNotificationAction) { - notificationsList.runPendingAction(); - notificationsList.requestNotificationAction = false; - } - } - function onFailed() { + function onUnlockSucceeded() { + // run pending action if successfully unlocked + if (notificationsList.requestNotificationAction) { + notificationsList.runPendingAction(); notificationsList.requestNotificationAction = false; } } + function onUnlockFailed() { + notificationsList.requestNotificationAction = false; + } + } + + PlasmaCore.ColorScope { + anchors.fill: parent + anchors.topMargin: root.topMargin + anchors.bottomMargin: root.bottomMargin + anchors.leftMargin: root.leftMargin + anchors.rightMargin: root.rightMargin + colorGroup: PlasmaCore.Theme.NormalColorGroup + MobileShell.NotificationsWidget { id: notificationsList anchors.fill: parent historyModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel actionsRequireUnlock: true - historyModel: rect.notificationsModel + historyModel: root.notificationsModel property bool requestNotificationAction: false diff --git a/look-and-feel/contents/lockscreen/PasswordBar.qml b/look-and-feel/contents/lockscreen/PasswordBar.qml index f3248b6d..2fbcbc56 100644 --- a/look-and-feel/contents/lockscreen/PasswordBar.qml +++ b/look-and-feel/contents/lockscreen/PasswordBar.qml @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Devin Lin + * SPDX-FileCopyrightText: 2020-2022 Devin Lin * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -7,67 +7,77 @@ import QtQuick 2.12 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.12 + import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.workspace.keyboardlayout 1.0 -import org.kde.kirigami 2.12 as Kirigami import org.kde.plasma.workspace.keyboardlayout 1.0 as Keyboards +import org.kde.kirigami 2.12 as Kirigami + Rectangle { id: root implicitHeight: PlasmaCore.Units.gridUnit * 2.5 + required property var lockScreenState + // toggle between pin and password mode property bool isPinMode: true - property string password - // for displaying temporary number in pin dot display - property int previewCharIndex + property int previewCharIndex: -2 + + property string pinLabel: qsTr("Enter PIN") - property string pinLabel property bool keypadOpen - // if waiting for result of auth - property bool waitingForAuth: false + readonly property color headerTextColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.75*255}) + readonly property color headerTextInactiveColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.4*255}) - property color headerTextColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.75*255}) - property color headerTextInactiveColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.4*255}) - - signal changePassword(); - - // keypad functions - function reset() { - waitingForAuth = false; - root.password = ""; - changePassword(); - root.pinLabel = qsTr("Enter PIN"); + // model for shown dots + // we need to use a listmodel to avoid all delegates from reloading + ListModel { + id: dotDisplayModel } + Connections { + target: root.lockScreenState + + function onUnlockSucceeded() { + root.pinLabel = qsTr("Logging in..."); + } + + function onUnlockFailed() { + root.pinLabel = qsTr("Wrong PIN"); + } + + function onPasswordChanged() { + while (root.lockScreenState.password.length < dotDisplayModel.count) { + dotDisplayModel.remove(dotDisplayModel.count - 1); + } + while (root.lockScreenState.password.length > dotDisplayModel.count) { + dotDisplayModel.append({"char": root.lockScreenState.password.charAt(dotDisplayModel.count)}); + } + } + } + + // keypad functions function backspace() { - if (!root.waitingForAuth) { + if (!lockScreenState.waitingForAuth) { root.previewCharIndex = -2; - root.password = root.password.substr(0, root.password.length - 1); - changePassword(); + lockScreenState.password = lockScreenState.password.substr(0, lockScreenState.password.length - 1); } } function clear() { - if (!root.waitingForAuth) { + if (!lockScreenState.waitingForAuth) { root.previewCharIndex = -2; - root.password = ""; - changePassword(); + lockScreenState.resetPassword(); } } function enter() { - if (root.password !== "") { // prevent typing lock when password is empty - root.waitingForAuth = true; - } + lockScreenState.tryPassword(); - // don't try to unlock if there is a timeout (unlock once unlocked) - if (!authenticator.graceLocked) { - authenticator.tryUnlock(root.password); - } if (keypadOpen && !isPinMode) { // make sure keyboard doesn't close openKeyboardTimer.restart(); @@ -75,13 +85,14 @@ Rectangle { } function keyPress(data) { - if (!root.waitingForAuth) { + if (!lockScreenState.waitingForAuth) { + if (root.pinLabel !== qsTr("Enter PIN")) { root.pinLabel = qsTr("Enter PIN"); } - root.previewCharIndex = root.password.length; - root.password += data - changePassword(); + + root.previewCharIndex = lockScreenState.password.length; + lockScreenState.password += data // trigger turning letter into dot later letterTimer.restart(); @@ -108,19 +119,6 @@ Rectangle { } } - // we need to use a listmodel to avoid all delegates from reloading - ListModel { - id: dotDisplayModel - } - onPasswordChanged: { - while (password.length < dotDisplayModel.count) { - dotDisplayModel.remove(dotDisplayModel.count - 1); - } - while (password.length > dotDisplayModel.count) { - dotDisplayModel.append({"char": password.charAt(dotDisplayModel.count)}); - } - } - // hidden textfield so that the virtual keyboard shows up TextField { id: textField @@ -139,19 +137,22 @@ Rectangle { property string prevText: "" Connections { - target: root - function onChangePassword() { - if (textField.text != root.password) { + target: root.lockScreenState + + function onPasswordChanged() { + if (textField.text != root.lockScreenState.password) { textField.externalEdit = true; - textField.text = root.password; + textField.text = root.lockScreenState.password; } } } + onEditingFinished: { if (textField.focus) { root.enter(); } } + onTextChanged: { if (!externalEdit) { if (prevText.length > text.length) { // backspace @@ -194,7 +195,7 @@ Rectangle { // label ("wrong pin", "enter pin") Label { - opacity: password.length === 0 ? 1 : 0 + opacity: root.lockScreenState.password.length === 0 ? 1 : 0 anchors.centerIn: parent text: root.pinLabel font.pointSize: 12 @@ -255,7 +256,7 @@ Rectangle { scale: 0 anchors.fill: parent radius: width - color: root.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth + color: lockScreenState.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth PropertyAnimation { id: dotAnimation @@ -269,7 +270,7 @@ Rectangle { id: charLabel scale: 0 anchors.centerIn: parent - color: root.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth + color: lockScreenState.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth text: model.char font.pointSize: 12 diff --git a/look-and-feel/metadata.desktop b/look-and-feel/metadata.desktop index 5a8bd595..14051695 100644 --- a/look-and-feel/metadata.desktop +++ b/look-and-feel/metadata.desktop @@ -128,4 +128,5 @@ X-KDE-PluginInfo-License=GPLv2+ X-KDE-PluginInfo-Name=org.kde.plasma.phone X-KDE-PluginInfo-Version=0.1 X-KDE-PluginInfo-Website= +X-Plasma-APIVersion=2 X-Plasma-MainScript=defaults