mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
lockscreen: Refactor swipe and use mobileshell notification component
This commit is contained in:
parent
dca8064ca2
commit
d9719b8845
12 changed files with 618 additions and 668 deletions
|
|
@ -36,6 +36,7 @@ TaskSwitcher 1.0 taskswitcher/TaskSwitcher.qml
|
|||
KRunnerWidget 1.0 widgets/krunner/KRunnerWidget.qml
|
||||
MediaControlsWidget 1.0 widgets/mediacontrols/MediaControlsWidget.qml
|
||||
NotificationsWidget 1.0 widgets/notifications/NotificationsWidget.qml
|
||||
NotificationsModelType 1.0 widgets/notifications/NotificationsModelType.qml
|
||||
|
||||
# /
|
||||
singleton HomeScreenControls 1.0 HomeScreenControls.qml
|
||||
|
|
|
|||
|
|
@ -20,7 +20,19 @@ import org.kde.kcoreaddons 1.0 as KCoreAddons
|
|||
|
||||
Item {
|
||||
id: notificationItem
|
||||
required property NotificationManager.Notifications notificationsModel
|
||||
|
||||
required property var notificationsModel
|
||||
|
||||
required property int notificationsModelType
|
||||
|
||||
/**
|
||||
* Whether the notification is allowed to invoke any action, or if it should instead
|
||||
* emit the runActionRequested(action) signal, containing the code to run.
|
||||
*
|
||||
* This is useful for cases like the lockscreen, where actions should only be run after
|
||||
* the user logs in.
|
||||
*/
|
||||
property bool requestToInvoke: false
|
||||
|
||||
property var model
|
||||
property int modelIndex
|
||||
|
|
@ -29,7 +41,7 @@ Item {
|
|||
|
||||
readonly property int notificationType: model.type
|
||||
|
||||
readonly property bool inGroup: model.isInGroup
|
||||
readonly property bool inGroup: model.isInGroup || false
|
||||
readonly property bool inHistory: true
|
||||
|
||||
readonly property string applicationIconSource: model.applicationIconName
|
||||
|
|
@ -87,7 +99,13 @@ Item {
|
|||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This signal is emitted and intended for the parent to make its own decision
|
||||
* on whether to run the requested notification action.
|
||||
*/
|
||||
signal runActionRequested()
|
||||
|
||||
signal actionInvoked(string actionName)
|
||||
signal replied(string text)
|
||||
signal openUrl(string url)
|
||||
|
|
@ -97,45 +115,124 @@ Item {
|
|||
signal resumeJobClicked
|
||||
signal killJobClicked
|
||||
|
||||
onActionInvoked: {
|
||||
if (actionName === "default") {
|
||||
notificationsModel.invokeDefaultAction(notificationsModel.index(modelIndex, 0));
|
||||
} else {
|
||||
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName);
|
||||
}
|
||||
|
||||
expire();
|
||||
}
|
||||
onOpenUrl: {
|
||||
Qt.openUrlExternally(url);
|
||||
expire();
|
||||
}
|
||||
onFileActionInvoked: {
|
||||
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
|
||||
close();
|
||||
} else {
|
||||
expire();
|
||||
}
|
||||
}
|
||||
onSuspendJobClicked: notificationsModel.suspendJob(notificationsModel.index(modelIndex, 0))
|
||||
onResumeJobClicked: notificationsModel.resumeJob(notificationsModel.index(modelIndex, 0))
|
||||
onKillJobClicked: notificationsModel.killJob(notificationsModel.index(modelIndex, 0))
|
||||
|
||||
function expire() {
|
||||
if (model.resident) {
|
||||
model.expired = true;
|
||||
} else {
|
||||
notificationsModel.expire(notificationsModel.index(modelIndex, 0));
|
||||
if (notificationsModelType === NotificationsModelType.WatchedNotificationsModel) {
|
||||
notificationsModel.expire(model.notificationId);
|
||||
} else if (notificationsModelType === NotificationsModelType.NotificationsModel) {
|
||||
notificationsModel.expire(notificationsModel.index(modelIndex, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
notificationsModel.close(notificationsModel.index(modelIndex, 0));
|
||||
if (notificationsModelType === NotificationsModelType.WatchedNotificationsModel) {
|
||||
notificationsModel.close(model.notificationId);
|
||||
} else if (notificationsModelType === NotificationsModelType.NotificationsModel) {
|
||||
notificationsModel.close(notificationsModel.index(modelIndex, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO call
|
||||
function configure() {
|
||||
notificationsModel.configure(notificationsModel.index(modelIndex, 0))
|
||||
}
|
||||
|
||||
property var pendingAction: () => {}
|
||||
function runPendingAction() {
|
||||
pendingAction();
|
||||
}
|
||||
|
||||
onActionInvoked: {
|
||||
let action = () => {
|
||||
if (notificationsModelType === NotificationsModelType.WatchedNotificationsModel) {
|
||||
if (actionName === "") {
|
||||
notificationsModel.invokeDefaultAction(model.notificationId);
|
||||
} else {
|
||||
notificationsModel.invokeAction(notificationItem.model.notificationId, actionName);
|
||||
}
|
||||
} else if (notificationsModelType === NotificationsModelType.NotificationsModel) {
|
||||
if (actionName === "default") {
|
||||
notificationsModel.invokeDefaultAction(notificationsModel.index(modelIndex, 0));
|
||||
} else {
|
||||
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName);
|
||||
}
|
||||
}
|
||||
expire();
|
||||
}
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
onOpenUrl: {
|
||||
let action = () => {
|
||||
Qt.openUrlExternally(url);
|
||||
expire();
|
||||
}
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
onFileActionInvoked: {
|
||||
let action = () => {
|
||||
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
|
||||
close();
|
||||
} else {
|
||||
expire();
|
||||
}
|
||||
}
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
onSuspendJobClicked: {
|
||||
let action = () => notificationsModel.suspendJob(notificationsModel.index(modelIndex, 0));
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
onResumeJobClicked: {
|
||||
let action = () => notificationsModel.resumeJob(notificationsModel.index(modelIndex, 0));
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
onKillJobClicked: {
|
||||
let action = () => notificationsModel.killJob(notificationsModel.index(modelIndex, 0));
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
*/
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Layouts 1.1
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import "util.js" as Util
|
|||
// notification properties are in BaseNotificationItem
|
||||
BaseNotificationItem {
|
||||
id: notificationItem
|
||||
implicitHeight: mainCard.implicitHeight
|
||||
implicitHeight: mainCard.implicitHeight + mainCard.anchors.topMargin + notificationHeading.height
|
||||
|
||||
// notification heading for groups with one element
|
||||
NotificationGroupHeader {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
QtObject {
|
||||
enum ModelType {
|
||||
NotificationsModel, // used on the shell
|
||||
WatchedNotificationsModel // used on the lockscreen
|
||||
}
|
||||
}
|
||||
|
|
@ -26,15 +26,56 @@ import org.kde.notificationmanager 1.0 as NotificationManager
|
|||
Item {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* The notification model for the widget.
|
||||
*/
|
||||
property var historyModel: []
|
||||
|
||||
/**
|
||||
* The type of notification model used for the widget.
|
||||
*/
|
||||
property int historyModelType: NotificationsModelType.NotificationsModel
|
||||
|
||||
/**
|
||||
* The notification model settings for the widget.
|
||||
*/
|
||||
property var notificationSettings: NotificationManager.Settings {}
|
||||
|
||||
/**
|
||||
* Whether invoking notification actions requires authentiation of some sort.
|
||||
*
|
||||
* If set to true, any attempted invoking will trigger the unlockRequested() signal.
|
||||
* Any consumers can then call the runPendingAction() function if authenticated to proceed
|
||||
* executing the notification action.
|
||||
*/
|
||||
property bool actionsRequireUnlock: false
|
||||
|
||||
/**
|
||||
* Whether the widget has notifications.
|
||||
*/
|
||||
readonly property bool hasNotifications: list.count > 0
|
||||
|
||||
enum ModelType {
|
||||
NotificationsModel, // used in the logged-in shell
|
||||
WatchedNotificationsModel // used on the lockscreen
|
||||
}
|
||||
|
||||
signal unlockRequested()
|
||||
|
||||
function runPendingAction() {
|
||||
list.pendingNotificationWithAction.runPendingAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the history of the notification model.
|
||||
*/
|
||||
function clearHistory() {
|
||||
historyModel.clear(NotificationManager.Notifications.ClearExpired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the system notification settings.
|
||||
*/
|
||||
function openNotificationSettings() {
|
||||
MobileShell.ShellUtil.executeCommand("plasma-open-settings kcm_notifications");
|
||||
}
|
||||
|
|
@ -52,6 +93,8 @@ Item {
|
|||
model: historyModel
|
||||
currentIndex: -1
|
||||
|
||||
property var pendingNotificationWithAction
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
|
|
@ -72,7 +115,7 @@ Item {
|
|||
width: parent.width - (PlasmaCore.Units.largeSpacing * 4)
|
||||
|
||||
text: i18n("Notification service not available")
|
||||
visible: list.count === 0 && !NotificationManager.Server.valid
|
||||
visible: list.count === 0 && !NotificationManager.Server.valid && historyModelType === NotificationsModelType.NotificationsModel
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
// Checking valid to avoid creating ServerInfo object if everything is alright
|
||||
|
|
@ -153,23 +196,41 @@ Item {
|
|||
spacing: PlasmaCore.Units.smallSpacing
|
||||
|
||||
NotificationItem {
|
||||
id: notificationItem
|
||||
Layout.fillWidth: true
|
||||
|
||||
model: delegateLoader.model
|
||||
modelIndex: delegateLoader.index
|
||||
notificationsModel: historyModel
|
||||
notificationsModel: root.historyModel
|
||||
notificationsModelType: root.historyModelType
|
||||
timeSource: timeDataSource
|
||||
|
||||
requestToInvoke: root.actionsRequireUnlock
|
||||
onRunActionRequested: {
|
||||
list.pendingNotificationWithAction = notificationItem;
|
||||
root.unlockRequested();
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents3.ToolButton {
|
||||
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
|
||||
text: model.isGroupExpanded ? i18n("Show Fewer")
|
||||
: i18nc("Expand to show n more notifications",
|
||||
"Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
|
||||
visible: (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
|
||||
&& delegateLoader.ListView.nextSection !== delegateLoader.ListView.section
|
||||
onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded)
|
||||
Loader {
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: active
|
||||
active: {
|
||||
// if we have the WatchedNotificationsModel, we don't have notification grouping support
|
||||
if (typeof model.groupChildrenCount === 'undefined')
|
||||
return false;
|
||||
|
||||
return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
|
||||
&& delegateLoader.ListView.nextSection !== delegateLoader.ListView.section
|
||||
}
|
||||
|
||||
sourceComponent: PlasmaComponents3.ToolButton {
|
||||
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
|
||||
text: model.isGroupExpanded ? i18n("Show Fewer")
|
||||
: i18nc("Expand to show n more notifications",
|
||||
"Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
|
||||
onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
import QtQuick 2.10
|
||||
import org.kde.kirigami 2.11 as Kirigami
|
||||
|
||||
MouseArea {
|
||||
id: delegate
|
||||
|
||||
property Item contentItem
|
||||
property bool draggable: false
|
||||
signal dismissRequested
|
||||
|
||||
anchors.fill: contentItem
|
||||
implicitWidth: contentItem ? contentItem.implicitWidth : 0
|
||||
implicitHeight: contentItem ? contentItem.implicitHeight : 0
|
||||
opacity: 1 - Math.min(1, 1.5 * Math.abs(x) / width)
|
||||
|
||||
drag {
|
||||
axis: Drag.XAxis
|
||||
target: draggable && Kirigami.Settings.tabletMode ? this : null
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (Math.abs(x) > width / 2) {
|
||||
delegate.dismissRequested();
|
||||
} else {
|
||||
slideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: slideAnim
|
||||
target: delegate
|
||||
property: "x"
|
||||
to: 0
|
||||
duration: PlasmaCore.Units.longDuration
|
||||
}
|
||||
}
|
||||
114
look-and-feel/contents/lockscreen/FlickContainer.qml
Normal file
114
look-and-feel/contents/lockscreen/FlickContainer.qml
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.plasma.core 2.1 as PlasmaCore
|
||||
|
||||
Flickable {
|
||||
id: root
|
||||
|
||||
property int position: 0
|
||||
|
||||
required property real keypadHeight
|
||||
|
||||
function cancelAnimations() {
|
||||
positionAnim.stop();
|
||||
}
|
||||
|
||||
function goToOpenPosition() {
|
||||
positionAnim.to = keypadHeight;
|
||||
positionAnim.restart();
|
||||
}
|
||||
|
||||
function goToClosePosition() {
|
||||
positionAnim.to = 0;
|
||||
positionAnim.restart();
|
||||
}
|
||||
|
||||
function updateState() {
|
||||
if (movingUp) {
|
||||
goToOpenPosition();
|
||||
} else {
|
||||
goToClosePosition();
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on position {
|
||||
id: positionAnim
|
||||
duration: PlasmaCore.Units.longDuration * 2
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
// we use flickable solely for capturing flicks, not positioning elements
|
||||
contentWidth: width
|
||||
contentHeight: height + 99999
|
||||
contentX: 0
|
||||
contentY: startContentY
|
||||
|
||||
readonly property real startContentY: contentHeight / 2
|
||||
|
||||
property bool positionChangedDueToFlickable: false
|
||||
|
||||
property int oldPosition: position
|
||||
property bool movingUp: false
|
||||
|
||||
onPositionChanged: {
|
||||
movingUp = oldPosition <= position;
|
||||
oldPosition = position;
|
||||
|
||||
// ensure that flickable is not moving when other sources are changing position
|
||||
if (!positionChangedDueToFlickable) {
|
||||
cancelMovement();
|
||||
}
|
||||
positionChangedDueToFlickable = true;
|
||||
}
|
||||
|
||||
// update position from flickable movement
|
||||
property real oldContentY
|
||||
onContentYChanged: {
|
||||
positionChangedDueToFlickable = true;
|
||||
position += (contentY - oldContentY);
|
||||
oldContentY = contentY;
|
||||
}
|
||||
|
||||
onMovementStarted: cancelAnimations();
|
||||
onMovementEnded: {
|
||||
if (!positionAnim.running) {
|
||||
updateState();
|
||||
}
|
||||
resetPosition();
|
||||
}
|
||||
onFlickEnded: resetPosition();
|
||||
|
||||
onDraggingChanged: {
|
||||
if (!dragging) {
|
||||
cancelMovement();
|
||||
resetPosition();
|
||||
if (!positionAnim.running) {
|
||||
root.updateState();
|
||||
}
|
||||
} else {
|
||||
cancelAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
function cancelMovement() {
|
||||
root.cancelFlick();
|
||||
|
||||
// HACK: cancelFlick() doesn't seem to cancel flicks...
|
||||
root.flick(-horizontalVelocity, -verticalVelocity);
|
||||
}
|
||||
|
||||
function resetPosition() {
|
||||
positionChangedDueToFlickable = true;
|
||||
oldContentY = startContentY;
|
||||
contentY = startContentY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
* SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 1.1
|
||||
|
|
@ -29,20 +29,21 @@ PlasmaCore.ColorScope {
|
|||
anchors.fill: parent
|
||||
|
||||
function isPinDrawerOpen() {
|
||||
return passwordFlickable.contentY === passwordFlickable.columnHeight;
|
||||
return flickable.openFactor >= 1;
|
||||
}
|
||||
|
||||
function askPassword() {
|
||||
showPasswordAnim.restart();
|
||||
flickable.goToOpenPosition();
|
||||
}
|
||||
NumberAnimation {
|
||||
id: showPasswordAnim
|
||||
target: passwordFlickable
|
||||
property: "contentY"
|
||||
from: 0
|
||||
to: passwordFlickable.contentHeight - passwordFlickable.height
|
||||
duration: PlasmaCore.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
|
||||
Notifications.WatchedNotificationsModel {
|
||||
id: notifModel
|
||||
}
|
||||
|
||||
Image {
|
||||
id: wallpaper
|
||||
anchors.fill: parent
|
||||
source: "/home/devin/Pictures/Wallpaper/1920x1080_1620115524334.jpeg"
|
||||
}
|
||||
|
||||
// blur background once keypad is open
|
||||
|
|
@ -65,236 +66,231 @@ PlasmaCore.ColorScope {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Notifications.WatchedNotificationsModel {
|
||||
id: notifModel
|
||||
}
|
||||
|
||||
// header bar
|
||||
Loader {
|
||||
id: headerBar
|
||||
asynchronous: true
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: PlasmaCore.Units.gridUnit * 1.25
|
||||
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
|
||||
sourceComponent: MobileShell.StatusBar {
|
||||
id: statusBar
|
||||
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
|
||||
backgroundColor: "transparent"
|
||||
|
||||
showSecondRow: false
|
||||
showDropShadow: true
|
||||
showTime: false
|
||||
disableSystemTray: true // HACK: prevent SIGABRT
|
||||
}
|
||||
}
|
||||
|
||||
// phone lockscreen component
|
||||
Loader {
|
||||
id: phoneComponent
|
||||
visible: !isWidescreen
|
||||
active: visible
|
||||
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
|
||||
|
||||
asynchronous: true
|
||||
z: passwordFlickable.contentY === 0 ? 5 : 0 // in front of password flickable when closed
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: scrollUpIcon.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: item && !root.notificationsShown ? Math.round(root.height / 2 - (item.implicitHeight / 2 + PlasmaCore.Units.gridUnit * 2)) : PlasmaCore.Units.gridUnit * 5
|
||||
bottomMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
Behavior on anchors.topMargin {
|
||||
NumberAnimation {
|
||||
duration: loadTimer.running ? 0 : PlasmaCore.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
// avoid topMargin animation when item is being loaded
|
||||
onLoaded: loadTimer.restart();
|
||||
Timer {
|
||||
id: loadTimer
|
||||
interval: PlasmaCore.Units.longDuration
|
||||
}
|
||||
|
||||
// move while swiping up
|
||||
transform: Translate { y: Math.round((1 - phoneComponent.opacity) * (-root.height / 6)) }
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
id: phoneClockComponent
|
||||
spacing: 0
|
||||
|
||||
Clock {
|
||||
id: phoneClock
|
||||
alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: PlasmaCore.Units.gridUnit * 2 // keep spacing even if media controls are gone
|
||||
}
|
||||
MobileShell.MediaControlsWidget {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
|
||||
Layout.leftMargin: PlasmaCore.Units.gridUnit
|
||||
Layout.rightMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
|
||||
NotificationsList {
|
||||
id: phoneNotificationsList
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: PlasmaCore.Units.gridUnit
|
||||
z: passwordFlickable.contentY === 0 ? 5 : 0 // prevent mousearea from interfering with pin drawer
|
||||
onCountChanged: root.notificationsShown = count !== 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tablet lockscreen component
|
||||
Loader {
|
||||
id: tabletComponent
|
||||
visible: isWidescreen
|
||||
active: visible
|
||||
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
|
||||
|
||||
asynchronous: true
|
||||
z: passwordFlickable.contentY === 0 ? 5 : 0 // in front of password flickable when closed
|
||||
anchors.top: headerBar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: scrollUpIcon.top
|
||||
|
||||
// move while swiping up
|
||||
transform: Translate { y: Math.round((1 - phoneComponent.opacity) * (-root.height / 6)) }
|
||||
|
||||
sourceComponent: Item {
|
||||
Item {
|
||||
id: tabletClockComponent
|
||||
width: parent.width / 2
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
leftMargin: PlasmaCore.Units.gridUnit * 3
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: tabletLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: PlasmaCore.Units.gridUnit
|
||||
|
||||
Clock {
|
||||
id: tabletClock
|
||||
alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 20
|
||||
}
|
||||
MobileShell.MediaControlsWidget {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tablet notifications list
|
||||
ColumnLayout {
|
||||
id: tabletNotificationsList
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: tabletClockComponent.right
|
||||
right: parent.right
|
||||
rightMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
|
||||
NotificationsList {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: parent.height
|
||||
Layout.minimumHeight: this.notificationListHeight
|
||||
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 15
|
||||
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
|
||||
onCountChanged: root.notificationsShown = count !== 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scroll up icon
|
||||
PlasmaCore.IconItem {
|
||||
id: scrollUpIcon
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: PlasmaCore.Units.gridUnit + passwordFlickable.contentY * 0.5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
implicitWidth: PlasmaCore.Units.iconSizes.smallMedium
|
||||
implicitHeight: PlasmaCore.Units.iconSizes.smallMedium
|
||||
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
|
||||
|
||||
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
|
||||
source: "arrow-up"
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: passwordFlickable
|
||||
|
||||
FlickContainer {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
|
||||
property int columnHeight: PlasmaCore.Units.gridUnit * 20
|
||||
property int oldContentY: contentY
|
||||
property real openFactor: position / keypadHeight
|
||||
|
||||
height: columnHeight + root.height
|
||||
contentHeight: columnHeight + root.height
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
Component.onCompleted: flickable.goToClosePosition()
|
||||
|
||||
// always snap to end (either hidden or shown)
|
||||
onMovementEnded: {
|
||||
if (!atYBeginning && !atYEnd) {
|
||||
if (contentY > columnHeight - contentY) {
|
||||
flick(0, -1000);
|
||||
} else {
|
||||
flick(0, 1000);
|
||||
onPositionChanged: {
|
||||
if (position > keypadHeight) {
|
||||
position = keypadHeight;
|
||||
} else if (position < 0) {
|
||||
position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
keypadHeight: PlasmaCore.Units.gridUnit * 20
|
||||
|
||||
Item {
|
||||
width: flickable.width
|
||||
height: flickable.height
|
||||
y: flickable.contentY // effectively anchored to the screen
|
||||
|
||||
// header bar
|
||||
Loader {
|
||||
id: headerBar
|
||||
asynchronous: true
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: PlasmaCore.Units.gridUnit * 1.25
|
||||
opacity: 1 - flickable.openFactor
|
||||
sourceComponent: MobileShell.StatusBar {
|
||||
id: statusBar
|
||||
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
|
||||
backgroundColor: "transparent"
|
||||
|
||||
showSecondRow: false
|
||||
showDropShadow: true
|
||||
showTime: false
|
||||
disableSystemTray: true // HACK: prevent SIGABRT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wipe password if it is more than half way down the screen
|
||||
onContentYChanged: {
|
||||
if (contentY < columnHeight / 2 && oldContentY >= columnHeight / 2) {
|
||||
keypad.reset();
|
||||
|
||||
// phone lockscreen component
|
||||
Loader {
|
||||
id: phoneComponent
|
||||
visible: !isWidescreen
|
||||
active: visible
|
||||
opacity: 1 - flickable.openFactor
|
||||
|
||||
asynchronous: true
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: scrollUpIcon.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: item && !root.notificationsShown ? Math.round(root.height / 2 - (item.implicitHeight / 2 + PlasmaCore.Units.gridUnit * 2)) : PlasmaCore.Units.gridUnit * 5
|
||||
bottomMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
|
||||
Behavior on anchors.topMargin {
|
||||
NumberAnimation {
|
||||
duration: loadTimer.running ? 0 : PlasmaCore.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// avoid topMargin animation when item is being loaded
|
||||
onLoaded: loadTimer.restart();
|
||||
Timer {
|
||||
id: loadTimer
|
||||
interval: PlasmaCore.Units.longDuration
|
||||
}
|
||||
|
||||
// move while swiping up
|
||||
transform: Translate { y: Math.round((1 - phoneComponent.opacity) * (-root.height / 6)) }
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
id: phoneClockComponent
|
||||
spacing: 0
|
||||
|
||||
Clock {
|
||||
id: phoneClock
|
||||
alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: PlasmaCore.Units.gridUnit * 2 // keep spacing even if media controls are gone
|
||||
}
|
||||
|
||||
MobileShell.MediaControlsWidget {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
|
||||
Layout.leftMargin: PlasmaCore.Units.gridUnit
|
||||
Layout.rightMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
|
||||
NotificationsComponent {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: PlasmaCore.Units.gridUnit * (25 + 2) // clip margins
|
||||
topMargin: PlasmaCore.Units.gridUnit
|
||||
leftMargin: PlasmaCore.Units.gridUnit
|
||||
rightMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
}
|
||||
}
|
||||
oldContentY = contentY;
|
||||
}
|
||||
|
||||
// keypad area
|
||||
ColumnLayout {
|
||||
id: passwordLayout
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
width: parent.width
|
||||
spacing: PlasmaCore.Units.gridUnit
|
||||
// tablet lockscreen component
|
||||
Loader {
|
||||
id: tabletComponent
|
||||
visible: isWidescreen
|
||||
active: visible
|
||||
opacity: 1 - flickable.openFactor
|
||||
|
||||
asynchronous: true
|
||||
anchors.top: headerBar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: scrollUpIcon.top
|
||||
|
||||
// move while swiping up
|
||||
transform: Translate { y: Math.round((1 - phoneComponent.opacity) * (-root.height / 6)) }
|
||||
|
||||
sourceComponent: Item {
|
||||
Item {
|
||||
id: tabletClockComponent
|
||||
width: parent.width / 2
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
leftMargin: PlasmaCore.Units.gridUnit * 3
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: tabletLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: PlasmaCore.Units.gridUnit
|
||||
|
||||
Clock {
|
||||
id: tabletClock
|
||||
alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 20
|
||||
}
|
||||
MobileShell.MediaControlsWidget {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tablet notifications list
|
||||
ColumnLayout {
|
||||
id: tabletNotificationsList
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: tabletClockComponent.right
|
||||
right: parent.right
|
||||
rightMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
|
||||
NotificationsComponent {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: PlasmaCore.Units.gridUnit * 2
|
||||
Layout.bottomMargin: PlasmaCore.Units.gridUnit
|
||||
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 15
|
||||
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
|
||||
leftMargin: PlasmaCore.Units.gridUnit
|
||||
rightMargin: PlasmaCore.Units.gridUnit
|
||||
bottomMargin: PlasmaCore.Units.gridUnit
|
||||
topMargin: PlasmaCore.Units.gridUnit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scroll down icon
|
||||
// scroll up icon
|
||||
PlasmaCore.IconItem {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
id: scrollUpIcon
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: PlasmaCore.Units.gridUnit + flickable.position * 0.5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
implicitWidth: PlasmaCore.Units.iconSizes.smallMedium
|
||||
implicitHeight: PlasmaCore.Units.iconSizes.smallMedium
|
||||
implicitHeight: PlasmaCore.Units.iconSizes.smallMedium
|
||||
opacity: 1 - flickable.openFactor
|
||||
|
||||
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
|
||||
source: "arrow-down"
|
||||
opacity: Math.sin((Math.PI / 2) * (passwordFlickable.contentY / passwordFlickable.columnHeight) + 1.5 * Math.PI) + 1
|
||||
source: "arrow-up"
|
||||
}
|
||||
|
||||
// password keypad
|
||||
ColumnLayout {
|
||||
id: passwordLayout
|
||||
anchors.bottom: parent.bottom
|
||||
transform: Translate { y: flickable.keypadHeight - flickable.position }
|
||||
|
||||
width: parent.width
|
||||
spacing: PlasmaCore.Units.gridUnit
|
||||
|
||||
// scroll down icon
|
||||
PlasmaCore.IconItem {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: PlasmaCore.Units.iconSizes.smallMedium
|
||||
implicitHeight: PlasmaCore.Units.iconSizes.smallMedium
|
||||
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
|
||||
source: "arrow-down"
|
||||
opacity: Math.sin((Math.PI / 2) * flickable.openFactor + 1.5 * Math.PI) + 1
|
||||
}
|
||||
|
||||
Keypad {
|
||||
id: keypad
|
||||
focus: true
|
||||
swipeProgress: passwordFlickable.contentY / passwordFlickable.columnHeight
|
||||
Layout.fillWidth: true
|
||||
onPasswordChanged: {
|
||||
passwordFlickable.contentY = passwordFlickable.contentHeight - passwordFlickable.height
|
||||
Keypad {
|
||||
id: keypad
|
||||
focus: true
|
||||
swipeProgress: flickable.openFactor
|
||||
Layout.fillWidth: true
|
||||
onPasswordChanged: {
|
||||
passwordFlickable.contentY = passwordFlickable.contentHeight - passwordFlickable.height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
look-and-feel/contents/lockscreen/NotificationsComponent.qml
Normal file
64
look-and-feel/contents/lockscreen/NotificationsComponent.qml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Devin Lin <espidev@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||
import org.kde.notificationmanager 1.1 as Notifications
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
property real leftMargin: 0
|
||||
property real rightMargin: 0
|
||||
property real topMargin: 0
|
||||
property real bottomMargin: 0
|
||||
|
||||
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: authenticator
|
||||
function onSucceeded() {
|
||||
if (phoneNotificationsList.requestNotificationAction) {
|
||||
phoneNotificationsList.runPendingAction();
|
||||
phoneNotificationsList.requestNotificationAction = false;
|
||||
}
|
||||
}
|
||||
function onFailed() {
|
||||
phoneNotificationsList.requestNotificationAction = false;
|
||||
}
|
||||
}
|
||||
|
||||
MobileShell.NotificationsWidget {
|
||||
id: phoneNotificationsList
|
||||
anchors.fill: parent
|
||||
|
||||
historyModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel
|
||||
actionsRequireUnlock: true
|
||||
historyModel: notifModel
|
||||
|
||||
property bool requestNotificationAction: false
|
||||
|
||||
onHasNotificationsChanged: root.notificationsShown = hasNotifications
|
||||
onUnlockRequested: {
|
||||
requestNotificationAction = true;
|
||||
root.askPassword();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||
import org.kde.notificationmanager 1.1 as Notifications
|
||||
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
Item {
|
||||
id: notificationsRoot
|
||||
property alias notificationListHeight: notificationListView.contentHeight
|
||||
property int count: notificationListView.count
|
||||
clip: true
|
||||
|
||||
property var pendingAction: {"notificationId": 0, "actionName": ""}
|
||||
|
||||
Rectangle {
|
||||
z: 1
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
visible: !notificationListView.atYBeginning
|
||||
height: PlasmaCore.Units.gridUnit
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: 1
|
||||
color: Qt.rgba(1, 1, 1, 0.5)
|
||||
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
z: 1
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
visible: !notificationListView.atYEnd
|
||||
height: PlasmaCore.Units.gridUnit
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: 1
|
||||
color: Qt.rgba(1, 1, 1, 0.5)
|
||||
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: authenticator
|
||||
function onSucceeded() {
|
||||
if (notificationsRoot.pendingAction.notificationId !== 0) {
|
||||
if (notificationsRoot.pendingAction.actionName.length == 0) {
|
||||
notifModel.invokeDefaultAction(pendingAction.notificationId);
|
||||
} else {
|
||||
notifModel.invokeAction(pendingAction.notificationId, pendingAction.actionName);
|
||||
}
|
||||
|
||||
notificationsRoot.pendingAction = {"notificationId": 0, "actionName":""};
|
||||
}
|
||||
}
|
||||
function onFailed() {
|
||||
notificationsRoot.pendingAction = {"notificationId": 0, "actionName":""};
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notificationComponent
|
||||
ColumnLayout {
|
||||
width: notificationListView.width
|
||||
spacing: PlasmaCore.Units.smallSpacing
|
||||
|
||||
// insert application heading here once application grouping is implemented
|
||||
|
||||
SimpleNotification {
|
||||
notification: model
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: notificationListView
|
||||
model: notifModel
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
width: Math.min(PlasmaCore.Units.gridUnit * 25, parent.width - PlasmaCore.Units.gridUnit * 2)
|
||||
height: Math.min(contentHeight, parent.height) // don't take up the entire screen for notification list view
|
||||
|
||||
interactive: contentHeight > parent.height // only allow scrolling on notifications list if it is long enough
|
||||
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
|
||||
spacing: PlasmaCore.Units.gridUnit
|
||||
|
||||
delegate: Kirigami.DelegateRecycler {
|
||||
sourceComponent: notificationComponent
|
||||
}
|
||||
add: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: Kirigami.Units.shortDuration }
|
||||
NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: Kirigami.Units.shortDuration }
|
||||
}
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents3
|
||||
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||
import org.kde.plasma.extras 2.0 as PlasmaExtras
|
||||
import org.kde.notificationmanager 1.1 as Notifications
|
||||
|
||||
import org.kde.kquickcontrolsaddons 2.0 as KQCAddons
|
||||
|
||||
// meant to be temporary, until the notifications components in plasma-workspace are available to used
|
||||
// https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/notifications/package/contents/ui/NotificationItem.qml
|
||||
Item {
|
||||
id: notificationItem
|
||||
property var notification
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: notifLayout.height + PlasmaCore.Units.gridUnit
|
||||
|
||||
opacity: 1 - Math.min(1, 1.5 * Math.abs(rect.x) / width) // opacity during dismiss swipe
|
||||
|
||||
RectangularGlow {
|
||||
anchors.topMargin: 1
|
||||
anchors.leftMargin: 1
|
||||
anchors.fill: rect
|
||||
cornerRadius: rect.radius * 2
|
||||
glowRadius: 2
|
||||
spread: 0.2
|
||||
color: "#616161"
|
||||
}
|
||||
|
||||
// notification
|
||||
Rectangle {
|
||||
id: rect
|
||||
|
||||
radius: 5
|
||||
color: "white"
|
||||
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
|
||||
border.color: "#bdbdbd"
|
||||
border.width: 1
|
||||
ColumnLayout {
|
||||
id: notifLayout
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: PlasmaCore.Units.gridUnit * 0.5
|
||||
right: parent.right
|
||||
rightMargin: PlasmaCore.Units.gridUnit * 0.5
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: PlasmaCore.Units.smallSpacing / 2
|
||||
// notif body
|
||||
ColumnLayout {
|
||||
id: textLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
spacing: PlasmaCore.Units.gridUnit / 2
|
||||
|
||||
Label {
|
||||
text: notification.summary
|
||||
color: "#212121"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
maximumLineCount: 3
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: 11
|
||||
}
|
||||
Label {
|
||||
text: notification.body
|
||||
color: "#616161"
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: 10
|
||||
}
|
||||
}
|
||||
|
||||
// notification icon
|
||||
Item {
|
||||
id: iconContainer
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
Layout.preferredWidth: PlasmaCore.Units.iconSizes.large
|
||||
Layout.preferredHeight: PlasmaCore.Units.iconSizes.large
|
||||
Layout.topMargin: PlasmaCore.Units.smallSpacing
|
||||
Layout.bottomMargin: PlasmaCore.Units.smallSpacing
|
||||
|
||||
visible: iconItem.active || imageItem.active
|
||||
|
||||
PlasmaCore.IconItem {
|
||||
id: iconItem
|
||||
// don't show two identical icons
|
||||
readonly property bool active: valid && source != notification.applicationIconSource
|
||||
anchors.fill: parent
|
||||
usesPlasmaTheme: false
|
||||
smooth: true
|
||||
source: {
|
||||
let icon = notification.icon;
|
||||
if (typeof icon !== "string") return "";
|
||||
if (icon === "dialog-information") return "";
|
||||
return icon;
|
||||
}
|
||||
visible: active
|
||||
}
|
||||
|
||||
KQCAddons.QImageItem {
|
||||
id: imageItem
|
||||
readonly property bool active: !null && nativeWidth > 0
|
||||
anchors.fill: parent
|
||||
smooth: true
|
||||
fillMode: KQCAddons.QImageItem.PreserveAspectFit
|
||||
visible: active
|
||||
image: typeof notification.icon === "object" ? notification.icon : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: actionsflow
|
||||
Layout.fillWidth: true
|
||||
spacing: PlasmaCore.Units.smallSpacing
|
||||
layoutDirection: Qt.RightToLeft
|
||||
Repeater {
|
||||
id: actionRepeater
|
||||
|
||||
model: {
|
||||
var buttons = [];
|
||||
var actionNames = (notificationItem.notification.actionNames || []);
|
||||
var actionLabels = (notificationItem.notification.actionLabels || []);
|
||||
// HACK We want the actions to be right-aligned but Flow also reverses
|
||||
for (var i = actionNames.length - 1; i >= 0; --i) {
|
||||
buttons.push({
|
||||
actionName: actionNames[i],
|
||||
label: actionLabels[i]
|
||||
});
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
PlasmaComponents3.ToolButton {
|
||||
flat: false
|
||||
// why does it spit "cannot assign undefined to string" when a notification becomes expired?
|
||||
text: modelData.label || ""
|
||||
|
||||
onClicked: {
|
||||
if (notificationItem.notification.category === "x-kde.incoming-call") {
|
||||
notifModel.invokeAction(notificationItem.notification.notificationId, modelData.actionName);
|
||||
} else {
|
||||
notificationsRoot.pendingAction = {"notificationId": notificationItem.notification.notificationId,
|
||||
"actionName":modelData.actionName};
|
||||
root.askPassword();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// swipe gesture for dismissing notification (left/right)
|
||||
MouseArea {
|
||||
id: dismissSwipe
|
||||
anchors.fill: parent
|
||||
drag.axis: Drag.XAxis
|
||||
drag.target: rect
|
||||
onPressed: {
|
||||
let pos = mapToItem(actionsflow, mouse.x, mouse.y);
|
||||
if (actionsflow.childAt(pos.x, pos.y)) {
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
if (Math.abs(rect.x) > width / 2) { // dismiss notification when finished swipe
|
||||
notifModel.close(notificationItem.notification.notificationId);
|
||||
} else {
|
||||
slideAnim.restart();
|
||||
}
|
||||
if (notificationItem.notification.hasDefaultAction && Math.abs(rect.x) < PlasmaCore.Units.gridUnit) {
|
||||
if (notificationItem.notification.category === "x-kde.incoming-call") {
|
||||
notifModel.invokeDefaultAction(notificationItem.notification.notificationId);
|
||||
} else {
|
||||
notificationsRoot.pendingAction = {"notificationId": notificationItem.notification.notificationId,
|
||||
"actionName": ""};
|
||||
root.askPassword();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: slideAnim
|
||||
target: rect
|
||||
property: "x"
|
||||
to: 0
|
||||
duration: PlasmaCore.Units.longDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue