diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/dashboard/dash/Weather.qml | 6 | ||||
| -rw-r--r-- | modules/lock/Center.qml | 186 | ||||
| -rw-r--r-- | modules/lock/Content.qml | 80 | ||||
| -rw-r--r-- | modules/lock/Input.qml | 60 | ||||
| -rw-r--r-- | modules/lock/InputField.qml | 159 | ||||
| -rw-r--r-- | modules/lock/Lock.qml | 35 | ||||
| -rw-r--r-- | modules/lock/LockSurface.qml | 292 | ||||
| -rw-r--r-- | modules/lock/Pam.qml | 66 | ||||
| -rw-r--r-- | modules/lock/WeatherInfo.qml | 192 |
9 files changed, 850 insertions, 226 deletions
diff --git a/modules/dashboard/dash/Weather.qml b/modules/dashboard/dash/Weather.qml index 0160229..c90ccf0a 100644 --- a/modules/dashboard/dash/Weather.qml +++ b/modules/dashboard/dash/Weather.qml @@ -20,7 +20,7 @@ Item { anchors.left: parent.left animate: true - text: Weather.icon || "cloud_alert" + text: Weather.icon color: Colours.palette.m3secondary font.pointSize: Appearance.font.size.extraLarge * 2 } @@ -38,7 +38,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter animate: true - text: Config.services.useFahrenheit ? Weather.tempF : Weather.tempC + text: Weather.temp color: Colours.palette.m3primary font.pointSize: Appearance.font.size.extraLarge font.weight: 500 @@ -48,7 +48,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter animate: true - text: Weather.description || qsTr("No weather") + text: Weather.description elide: Text.ElideRight width: Math.min(implicitWidth, root.parent.width - icon.implicitWidth - info.anchors.leftMargin - Appearance.padding.large * 2) diff --git a/modules/lock/Center.qml b/modules/lock/Center.qml new file mode 100644 index 0000000..b288284 --- /dev/null +++ b/modules/lock/Center.qml @@ -0,0 +1,186 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.components.images +import qs.services +import qs.config +import qs.utils +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + required property var lock + readonly property list<string> timeComponents: Time.format(Config.services.useTwelveHourClock ? "hh:mm:A" : "hh:mm").split(":") + + Layout.preferredWidth: Config.lock.sizes.centerWidth + Layout.fillHeight: true + + spacing: Appearance.spacing.large * 2 + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: Appearance.spacing.small + + StyledText { + Layout.alignment: Qt.AlignVCenter + text: root.timeComponents[0] + color: Colours.palette.m3secondary + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignVCenter + text: ":" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + StyledText { + Layout.alignment: Qt.AlignVCenter + text: root.timeComponents[1] + color: Colours.palette.m3secondary + font.pointSize: Appearance.font.size.extraLarge * 3 + font.bold: true + } + + Loader { + Layout.leftMargin: Appearance.spacing.normal + Layout.alignment: Qt.AlignVCenter + + asynchronous: true + active: Config.services.useTwelveHourClock + visible: active + + sourceComponent: StyledText { + text: root.timeComponents[2] ?? "" + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.extraLarge * 2 + font.bold: true + } + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: -Appearance.padding.large * 2 + + text: Time.format("dddd, d MMMM yyyy") + color: Colours.palette.m3tertiary + font.pointSize: Appearance.font.size.extraLarge + font.family: Appearance.font.family.mono + font.bold: true + } + + StyledClippingRect { + Layout.topMargin: Appearance.spacing.large * 2 + Layout.alignment: Qt.AlignHCenter + + implicitWidth: Config.lock.sizes.centerWidth / 2 + implicitHeight: Config.lock.sizes.centerWidth / 2 + + color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.full + + MaterialIcon { + anchors.centerIn: parent + + text: "person" + fill: 1 + grade: 200 + font.pointSize: Math.floor(Config.lock.sizes.centerWidth / 4) + } + + CachingImage { + id: pfp + + anchors.fill: parent + path: `${Paths.stringify(Paths.home)}/.face` + } + } + + StyledRect { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: Config.lock.sizes.centerWidth * 0.8 + implicitHeight: input.implicitHeight + Appearance.padding.small * 2 + + color: Colours.palette.m3surfaceContainer + radius: Appearance.rounding.full + + focus: true + onActiveFocusChanged: { + if (!activeFocus) + forceActiveFocus(); + } + + Keys.onPressed: event => { + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) + inputField.placeholder.animate = false; + + root.lock.pam.handleKey(event); + } + + StateLayer { + hoverEnabled: false + cursorShape: Qt.IBeamCursor + + function onClicked(): void { + parent.forceActiveFocus(); + } + } + + RowLayout { + id: input + + anchors.fill: parent + anchors.margins: Appearance.padding.small + spacing: Appearance.spacing.normal + + MaterialIcon { + Layout.leftMargin: Appearance.padding.smaller + text: "lock" + } + + InputField { + id: inputField + + pam: root.lock.pam + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: enterIcon.implicitHeight + Appearance.padding.small * 2 + + color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2) + radius: Appearance.rounding.full + + StateLayer { + color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface + + function onClicked(): void { + root.lock.pam.passwd.start(); + } + } + + MaterialIcon { + id: enterIcon + + anchors.centerIn: parent + text: "arrow_forward" + color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface + font.weight: 500 + } + } + } + } + + component Anim: NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } +} diff --git a/modules/lock/Content.qml b/modules/lock/Content.qml new file mode 100644 index 0000000..b7d3ddb --- /dev/null +++ b/modules/lock/Content.qml @@ -0,0 +1,80 @@ +import qs.components +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +RowLayout { + id: root + + required property var lock + property real centerScale + + anchors.fill: parent + anchors.margins: Appearance.padding.large + + spacing: Appearance.spacing.large * 2 + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledRect { + Layout.fillWidth: true + implicitHeight: weather.implicitHeight + + topLeftRadius: Appearance.rounding.large + radius: Appearance.rounding.small + color: Colours.tPalette.m3surfaceContainer + + WeatherInfo { + id: weather + } + } + + StyledRect { + Layout.fillWidth: true + Layout.fillHeight: true + + bottomLeftRadius: Appearance.rounding.large + radius: Appearance.rounding.small + color: Colours.tPalette.m3surfaceContainer + } + } + + Center { + Layout.leftMargin: -(1 - scale) * implicitWidth / 2 + Layout.rightMargin: -(1 - scale) * implicitWidth / 2 + scale: root.centerScale + lock: root.lock + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledRect { + Layout.fillWidth: true + Layout.fillHeight: true + + topRightRadius: Appearance.rounding.large + radius: Appearance.rounding.small + color: Colours.tPalette.m3surfaceContainer + } + + StyledRect { + Layout.fillWidth: true + Layout.fillHeight: true + + bottomRightRadius: Appearance.rounding.large + radius: Appearance.rounding.small + color: Colours.tPalette.m3surfaceContainer + } + } + + component Anim: NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } +} diff --git a/modules/lock/Input.qml b/modules/lock/Input.qml index b989bb9..1e164b8 100644 --- a/modules/lock/Input.qml +++ b/modules/lock/Input.qml @@ -12,60 +12,40 @@ import QtQuick.Layouts ColumnLayout { id: root - required property WlSessionLockSurface lock + required property var lock property string passwordBuffer + Layout.preferredWidth: Config.lock.sizes.faceSize * 2 + Layout.fillWidth: false spacing: Appearance.spacing.large * 2 - RowLayout { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: Appearance.padding.large * 3 - Layout.maximumWidth: Config.lock.sizes.inputWidth - Appearance.rounding.large * 2 - - spacing: Appearance.spacing.large - - StyledClippingRect { - Layout.alignment: Qt.AlignVCenter - implicitWidth: Config.lock.sizes.faceSize - implicitHeight: Config.lock.sizes.faceSize - - radius: Appearance.rounding.large - color: Colours.tPalette.m3surfaceContainer + StyledRect { + Layout.fillWidth: true + implicitHeight: user.implicitHeight + Appearance.padding.small * 2 - MaterialIcon { - anchors.centerIn: parent + color: Colours.tPalette.m3surfaceContainer + radius: Appearance.rounding.small - text: "person" - fill: 1 - grade: 200 - font.pointSize: Config.lock.sizes.faceSize / 2 - } + RowLayout { + id: user - CachingImage { - anchors.fill: parent - path: `${Paths.stringify(Paths.home)}/.face` - } - } + anchors.centerIn: parent - ColumnLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - spacing: Appearance.spacing.small + spacing: Appearance.spacing.normal - StyledText { - Layout.fillWidth: true - text: qsTr("Welcome back, %1").arg(Quickshell.env("USER")) - font.pointSize: Appearance.font.size.extraLarge + MaterialIcon { + text: "account_circle" + font.pointSize: Appearance.font.size.large * 1.4 font.weight: 500 - elide: Text.ElideRight } StyledText { - Layout.fillWidth: true - text: qsTr("Logging in to %1").arg(Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP")) - color: Colours.palette.m3tertiary + // Layout.fillWidth: true + text: Quickshell.env("USER") font.pointSize: Appearance.font.size.large + // font.capitalization: Font.Capitalize + font.weight: 500 elide: Text.ElideRight } } @@ -121,7 +101,7 @@ ColumnLayout { onCompleted: res => { if (res === PamResult.Success) - return root.lock.unlock(); + return root.lock.lock.unlock(); if (res === PamResult.Error) placeholder.pamState = "error"; diff --git a/modules/lock/InputField.qml b/modules/lock/InputField.qml new file mode 100644 index 0000000..9472d41 --- /dev/null +++ b/modules/lock/InputField.qml @@ -0,0 +1,159 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.services +import qs.config +import Quickshell +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Pam pam + readonly property alias placeholder: placeholder + property string buffer + + Layout.fillWidth: true + Layout.fillHeight: true + + clip: true + + Connections { + target: root.pam + + function onBufferChanged(): void { + if (root.pam.buffer.length > root.buffer.length) { + charList.bindImWidth(); + } else if (root.pam.buffer.length === 0) { + charList.implicitWidth = charList.implicitWidth; + placeholder.animate = true; + } + + root.buffer = root.pam.buffer; + } + } + + StyledText { + id: placeholder + + anchors.centerIn: parent + + text: { + if (root.pam.active) + return qsTr("Loading..."); + if (root.pam.state === "error") + return qsTr("An error occured"); + if (root.pam.state === "max") + return qsTr("You have reached the maximum number of tries"); + if (root.pam.state === "fail") + return qsTr("Incorrect password"); + return qsTr("Enter your password"); + } + + animate: true + color: root.pam.active ? Colours.palette.m3secondary : root.pam.state ? Colours.palette.m3error : Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + font.family: Appearance.font.family.mono + + opacity: root.buffer ? 0 : 1 + + Behavior on opacity { + Anim {} + } + } + + ListView { + id: charList + + readonly property int fullWidth: count * (implicitHeight + spacing) - spacing + + function bindImWidth(): void { + imWidthBehavior.enabled = false; + implicitWidth = Qt.binding(() => fullWidth); + imWidthBehavior.enabled = true; + } + + anchors.centerIn: parent + anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0 + + implicitWidth: fullWidth + implicitHeight: Appearance.font.size.normal + + orientation: Qt.Horizontal + spacing: Appearance.spacing.small / 2 + interactive: false + + model: ScriptModel { + values: root.buffer.split("") + } + + delegate: StyledRect { + id: ch + + implicitWidth: implicitHeight + implicitHeight: charList.implicitHeight + + color: Colours.palette.m3onSurface + radius: Appearance.rounding.small / 2 + + opacity: 0 + scale: 0 + Component.onCompleted: { + opacity = 1; + scale = 1; + } + ListView.onRemove: removeAnim.start() + + SequentialAnimation { + id: removeAnim + + PropertyAction { + target: ch + property: "ListView.delayRemove" + value: true + } + ParallelAnimation { + Anim { + target: ch + property: "opacity" + to: 0 + } + Anim { + target: ch + property: "scale" + to: 0.5 + } + } + PropertyAction { + target: ch + property: "ListView.delayRemove" + value: false + } + } + + Behavior on opacity { + Anim {} + } + + Behavior on scale { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + } + + Behavior on implicitWidth { + id: imWidthBehavior + + Anim {} + } + } + + component Anim: NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } +} diff --git a/modules/lock/Lock.qml b/modules/lock/Lock.qml index d0f27ec..fc0bff8 100644 --- a/modules/lock/Lock.qml +++ b/modules/lock/Lock.qml @@ -6,52 +6,49 @@ import Quickshell.Io import Quickshell.Wayland Scope { - LazyLoader { - id: loader - WlSessionLock { - id: lock + WlSessionLock { + id: lock - property bool unlocked + signal unlock - locked: true + LockSurface { + lock: lock + pam: pam + } + } - onLockedChanged: { - if (!locked) - loader.active = false; - } + Pam { + id: pam - LockSurface { - lock: lock - } - } + lock: lock } CustomShortcut { name: "lock" description: "Lock the current session" - onPressed: loader.activeAsync = true + onPressed: lock.locked = true } CustomShortcut { name: "unlock" description: "Unlock the current session" - onPressed: loader.item.locked = false + onPressed: lock.unlock() } IpcHandler { target: "lock" function lock(): void { - loader.activeAsync = true; + lock.locked = true; } function unlock(): void { - loader.item.locked = false; + lock.unlock(); } function isLocked(): bool { - return loader.active; + return lock.locked; } } } diff --git a/modules/lock/LockSurface.qml b/modules/lock/LockSurface.qml index ed6895b..88bd1af 100644 --- a/modules/lock/LockSurface.qml +++ b/modules/lock/LockSurface.qml @@ -1,5 +1,7 @@ pragma ComponentBehavior: Bound +import qs.components +import qs.components.effects import qs.services import qs.config import Quickshell.Wayland @@ -10,31 +12,144 @@ WlSessionLockSurface { id: root required property WlSessionLock lock + required property Pam pam - property bool thisLocked - readonly property bool locked: thisLocked && !lock.unlocked + property bool locked - function unlock(): void { - lock.unlocked = true; - animDelay.start(); - } - - Component.onCompleted: thisLocked = true + Component.onCompleted: locked = true color: "transparent" - Timer { - id: animDelay + Connections { + target: root.lock - interval: Appearance.anim.durations.large - onTriggered: root.lock.locked = false + function onUnlock(): void { + root.locked = false; + unlockAnim.start(); + } } - Connections { - target: root.lock + SequentialAnimation { + id: unlockAnim + + ParallelAnimation { + Anim { + target: lockBg + properties: "implicitWidth,implicitHeight" + to: lockBg.size + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + Anim { + target: lockBg + property: "radius" + to: lockBg.size / 4 + } + Anim { + target: content + property: "opacity" + to: 0 + duration: Appearance.anim.durations.small + } + Anim { + target: lockIcon + property: "opacity" + to: 1 + } + Anim { + target: background + property: "opacity" + to: 0 + duration: Appearance.anim.durations.large + } + SequentialAnimation { + PauseAnimation { + duration: Appearance.anim.durations.small + } + Anim { + target: lockBg + property: "opacity" + to: 0 + } + } + } + PropertyAction { + target: root.lock + property: "locked" + value: false + } + } + + ParallelAnimation { + running: true - function onUnlockedChanged(): void { - background.opacity = 0; + Anim { + target: background + property: "opacity" + to: 1 + duration: Appearance.anim.durations.large + } + SequentialAnimation { + ParallelAnimation { + Anim { + target: lockBg + property: "scale" + to: 1 + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + Anim { + target: lockBg + property: "rotation" + to: 360 + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + } + ParallelAnimation { + Anim { + target: lockIcon + property: "rotation" + to: 360 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + Anim { + target: lockIcon + property: "opacity" + to: 0 + } + Anim { + target: content + property: "opacity" + to: 1 + } + Anim { + target: content + property: "centerScale" + to: 1 + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + Anim { + target: lockBg + property: "radius" + to: Appearance.rounding.large * 1.5 + } + Anim { + target: lockBg + property: "implicitWidth" + to: root.screen.height * Config.lock.sizes.heightMult * Config.lock.sizes.ratio + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + Anim { + target: lockBg + property: "implicitHeight" + to: root.screen.height * Config.lock.sizes.heightMult + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } } } @@ -43,138 +158,63 @@ WlSessionLockSurface { anchors.fill: parent captureSource: root.screen + opacity: 0 layer.enabled: true layer.effect: MultiEffect { + id: backgroundBlur + autoPaddingEnabled: false blurEnabled: true - blur: root.locked ? 1 : 0 + blur: 1 blurMax: 64 blurMultiplier: 1 - - Behavior on blur { - Anim {} - } - } - - Behavior on opacity { - Anim {} } } - Backgrounds { - id: backgrounds - - locked: root.locked - weatherWidth: weather.implicitWidth - buttonsWidth: buttons.item?.nonAnimWidth ?? 0 - buttonsHeight: buttons.item?.nonAnimHeight ?? 0 - statusWidth: status.nonAnimWidth ?? 0 - statusHeight: status.nonAnimHeight ?? 0 - isNormal: root.screen.width > Config.lock.sizes.smallScreenWidth - isLarge: root.screen.width > Config.lock.sizes.largeScreenWidth - - layer.enabled: true - layer.effect: MultiEffect { - shadowEnabled: true - blurMax: 15 - shadowColor: Qt.alpha(Colours.palette.m3shadow, 0.7) - } - } - - Clock { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.top - anchors.bottomMargin: -backgrounds.clockBottom - } - - Input { - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.bottom - anchors.topMargin: -backgrounds.inputTop - - lock: root - } - - WeatherInfo { - id: weather - - anchors.top: parent.bottom - anchors.right: parent.left - anchors.topMargin: -backgrounds.weatherTop - anchors.rightMargin: -backgrounds.weatherRight - } + StyledRect { + id: lockBg - Loader { - id: media + readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4 - active: root.screen.width > Config.lock.sizes.smallScreenWidth - asynchronous: true + anchors.centerIn: parent + implicitWidth: size + implicitHeight: size - state: root.screen.width > Config.lock.sizes.largeScreenWidth ? "tl" : "br" - states: [ - State { - name: "tl" + clip: true + color: Colours.tPalette.m3surface + radius: size / 4 + rotation: 180 + scale: 0 - AnchorChanges { - target: media - anchors.bottom: media.parent.top - anchors.right: media.parent.left - } - - PropertyChanges { - media.anchors.bottomMargin: -backgrounds.mediaY - media.anchors.rightMargin: -backgrounds.mediaX - } - }, - State { - name: "br" - - AnchorChanges { - target: media - anchors.top: media.parent.bottom - anchors.left: media.parent.right - } - - PropertyChanges { - media.anchors.topMargin: -backgrounds.mediaY - media.anchors.leftMargin: -backgrounds.mediaX - } - } - ] - - sourceComponent: MediaPlaying { - isLarge: root.screen.width > Config.lock.sizes.largeScreenWidth + Elevation { + anchors.fill: parent + radius: parent.radius + z: -1 + level: 3 + offset.y: 0 } - } - - Loader { - id: buttons - - active: root.screen.width > Config.lock.sizes.largeScreenWidth - asynchronous: true - anchors.top: parent.bottom - anchors.left: parent.right - anchors.topMargin: -backgrounds.buttonsTop - anchors.leftMargin: -backgrounds.buttonsLeft + MaterialIcon { + id: lockIcon - sourceComponent: Buttons {} - } - - Status { - id: status + anchors.centerIn: parent + text: "lock" + font.pointSize: Appearance.font.size.extraLarge * 4 + font.bold: true + rotation: 180 + } - anchors.bottom: parent.top - anchors.left: parent.right - anchors.bottomMargin: -backgrounds.statusBottom - anchors.leftMargin: -backgrounds.statusLeft + Content { + id: content - showNotifs: root.screen.width > Config.lock.sizes.largeScreenWidth + lock: root + opacity: 0 + } } component Anim: NumberAnimation { - duration: Appearance.anim.durations.large + duration: Appearance.anim.durations.normal easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.anim.curves.standard } diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml new file mode 100644 index 0000000..fa6e3d7 --- /dev/null +++ b/modules/lock/Pam.qml @@ -0,0 +1,66 @@ +import Quickshell +import Quickshell.Wayland +import Quickshell.Services.Pam +import QtQuick + +Scope { + id: root + + required property WlSessionLock lock + + readonly property alias passwd: passwd + readonly property bool active: passwd.active + property string state + property string buffer + + function handleKey(event: KeyEvent): void { + if (passwd.active) + return; + + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { + passwd.start(); + } else if (event.key === Qt.Key_Backspace) { + if (event.modifiers & Qt.ControlModifier) { + buffer = ""; + } else { + buffer = buffer.slice(0, -1); + } + } else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) { + // No illegal characters (you are insane if you use unicode in your password) + buffer += event.text; + } + } + + PamContext { + id: passwd + + onResponseRequiredChanged: { + if (!responseRequired) + return; + + respond(root.buffer); + root.buffer = ""; + } + + onCompleted: res => { + if (res === PamResult.Success) + return root.lock.unlock(); + + if (res === PamResult.Error) + root.state = "error"; + else if (res === PamResult.MaxTries) + root.state = "max"; + else if (res === PamResult.Failed) + root.state = "fail"; + + stateReset.restart(); + } + } + + Timer { + id: stateReset + + interval: 4000 + onTriggered: root.state = "" + } +} diff --git a/modules/lock/WeatherInfo.qml b/modules/lock/WeatherInfo.qml index b944d2f..5d10e7b 100644 --- a/modules/lock/WeatherInfo.qml +++ b/modules/lock/WeatherInfo.qml @@ -1,61 +1,177 @@ import qs.components import qs.services import qs.config +import qs.utils import QtQuick import QtQuick.Layouts -RowLayout { +ColumnLayout { id: root - Timer { - running: true - triggeredOnStart: true - repeat: true - interval: 900000 // 15 minutes - onTriggered: Weather.reload() + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: Appearance.padding.large * 2 + + spacing: Appearance.spacing.small + + StyledText { + Layout.topMargin: Appearance.padding.large * 2 + Layout.alignment: Qt.AlignHCenter + text: qsTr("Weather") + color: Colours.palette.m3primary + font.pointSize: Appearance.font.size.extraLarge + font.weight: 500 } - spacing: Appearance.spacing.large + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.large + + MaterialIcon { + animate: true + text: Weather.icon + color: Colours.palette.m3secondary + font.pointSize: Appearance.font.size.extraLarge * 2.5 + } + + ColumnLayout { + spacing: Appearance.spacing.small - MaterialIcon { - id: icon + StyledText { + Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - Layout.topMargin: Config.lock.sizes.border / 4 + animate: true + text: Weather.description + color: Colours.palette.m3secondary + font.pointSize: Appearance.font.size.large + font.weight: 500 + elide: Text.ElideRight + } - animate: true - text: Weather.icon || "cloud_alert" - color: Colours.palette.m3secondary - font.pointSize: Appearance.font.size.extraLarge * 2.5 - } + StyledText { + Layout.fillWidth: true - ColumnLayout { - Layout.alignment: Qt.AlignVCenter - Layout.topMargin: Config.lock.sizes.border / 4 - Layout.rightMargin: Config.lock.sizes.border / 2 + animate: true + text: qsTr("Humidity: %1%").arg(Weather.humidity) + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.normal + elide: Text.ElideRight + } + } - spacing: Appearance.spacing.small + Loader { + Layout.rightMargin: Appearance.padding.smaller + asynchronous: true + active: root.width > 400 + visible: active - StyledText { - Layout.fillWidth: true + sourceComponent: ColumnLayout { + spacing: Appearance.spacing.small - animate: true - text: Config.services.useFahrenheit ? Weather.tempF : Weather.tempC - color: Colours.palette.m3primary - horizontalAlignment: Text.AlignHCenter - font.pointSize: Appearance.font.size.extraLarge - font.weight: 500 + StyledText { + Layout.fillWidth: true + + animate: true + text: Weather.temp + color: Colours.palette.m3primary + horizontalAlignment: Text.AlignRight + font.pointSize: Appearance.font.size.extraLarge + font.weight: 500 + elide: Text.ElideLeft + } + + StyledText { + Layout.fillWidth: true + + animate: true + text: qsTr("Feels like: %1").arg(Weather.feelsLike) + color: Colours.palette.m3outline + horizontalAlignment: Text.AlignRight + font.pointSize: Appearance.font.size.smaller + elide: Text.ElideLeft + } + } } + } - StyledText { - Layout.fillWidth: true - Layout.maximumWidth: Config.lock.sizes.weatherWidth - icon.implicitWidth + RowLayout { + Layout.topMargin: Appearance.spacing.smaller + Layout.bottomMargin: Appearance.padding.large * 2 + Layout.fillWidth: true + spacing: Appearance.spacing.large - animate: true - text: Weather.description || qsTr("No weather") - horizontalAlignment: Text.AlignHCenter - font.pointSize: Appearance.font.size.large - elide: Text.ElideRight + Repeater { + model: { + const forecast = Weather.forecast; + let count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5; + if (!forecast) + return Array.from({ + length: count + }, () => null); + + const hours = []; + const hour = new Date().getHours(); + + const today = forecast[0].hourly; + const arr = [...today, ...forecast[1].hourly]; + for (let i = 0; i < arr.length; i++) { + const time = parseInt(arr[i].time, 10) / 100; + + if (i > today.length ? time < hour : time > hour) { + hours.push(arr[i]); + count--; + } + + if (count === 0) + break; + } + + return hours; + } + + ColumnLayout { + id: forecastHour + + required property var modelData + + Layout.fillWidth: true + spacing: Appearance.spacing.small + + StyledText { + Layout.fillWidth: true + text: { + if (!forecastHour.modelData) + return "00 AM"; + const hour = parseInt(forecastHour.modelData.time, 10) / 100; + return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`; + } + color: Colours.palette.m3outline + horizontalAlignment: Text.AlignHCenter + font.pointSize: Appearance.font.size.larger + } + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + text: forecastHour.modelData ? Icons.getWeatherIcon(forecastHour.modelData.weatherCode) : "cloud_alert" + font.pointSize: Appearance.font.size.extraLarge * 1.5 + font.weight: 500 + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C` + color: Colours.palette.m3secondary + font.pointSize: Appearance.font.size.larger + } + } } } + + Timer { + running: true + triggeredOnStart: true + repeat: true + interval: 900000 // 15 minutes + onTriggered: Weather.reload() + } } |