mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-21 12:35:52 +03:00
Make UserStatusSelector a dismissible page pushed onto the tray window
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
This commit is contained in:
parent
b90e79a6a0
commit
d86f25d215
10 changed files with 426 additions and 362 deletions
|
@ -1,7 +1,7 @@
|
|||
<RCC>
|
||||
<qresource prefix="/qml">
|
||||
<file>src/gui/UserStatusSelector.qml</file>
|
||||
<file>src/gui/UserStatusSelectorDialog.qml</file>
|
||||
<file>src/gui/UserStatusSelectorPage.qml</file>
|
||||
<file>src/gui/EmojiPicker.qml</file>
|
||||
<file>src/gui/UserStatusSelectorButton.qml</file>
|
||||
<file>src/gui/PredefinedStatusButton.qml</file>
|
||||
|
|
|
@ -23,274 +23,297 @@ import Style 1.0
|
|||
|
||||
ColumnLayout {
|
||||
id: rootLayout
|
||||
spacing: 0
|
||||
spacing: Style.standardSpacing * 2
|
||||
property NC.UserStatusSelectorModel userStatusSelectorModel
|
||||
|
||||
Label {
|
||||
Layout.topMargin: Style.standardSpacing * 2
|
||||
Layout.leftMargin: Style.standardSpacing
|
||||
Layout.rightMargin: Style.standardSpacing
|
||||
Layout.bottomMargin: Style.standardSpacing
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
font.bold: true
|
||||
text: qsTr("Online status")
|
||||
color: Style.ncTextColor
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: topButtonsLayout
|
||||
Column {
|
||||
// We use a normal column here because layouts often don't adjust to any custom
|
||||
// alignments for each other. If Item 2 is below Item 1, Item 2 will always set
|
||||
// its alignment in relation to Item 1 being in default alignment of vertically
|
||||
// centered. So when we set Item 2 to align top, even if Item 1 is aligned top,
|
||||
// Item 2 will align itself as if Item 1 were vertically centered.
|
||||
//
|
||||
// Since in this case we want to set everything to align top, we use the Column
|
||||
// which does this well, have it fill the height of the parent ColumnLayout,
|
||||
// pushing the bottom button box down.
|
||||
|
||||
Layout.margins: Style.standardSpacing
|
||||
Layout.alignment: Qt.AlignTop
|
||||
columns: 2
|
||||
rows: 2
|
||||
columnSpacing: Style.standardSpacing
|
||||
rowSpacing: Style.standardSpacing
|
||||
id: mainContentsLayout
|
||||
spacing: rootLayout.spacing
|
||||
|
||||
property int maxButtonHeight: 0
|
||||
function updateMaxButtonHeight(newHeight) {
|
||||
maxButtonHeight = Math.max(maxButtonHeight, newHeight)
|
||||
}
|
||||
|
||||
UserStatusSelectorButton {
|
||||
checked: NC.UserStatus.Online === userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.onlineIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Online")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.Online
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
Layout.preferredHeight: topButtonsLayout.maxButtonHeight
|
||||
onImplicitHeightChanged: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
Component.onCompleted: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
checked: NC.UserStatus.Away === userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.awayIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Away")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.Away
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
Layout.preferredHeight: topButtonsLayout.maxButtonHeight
|
||||
onImplicitHeightChanged: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
Component.onCompleted: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
checked: NC.UserStatus.DoNotDisturb === userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.dndIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Do not disturb")
|
||||
secondaryText: qsTr("Mute all notifications")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.DoNotDisturb
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
Layout.preferredHeight: topButtonsLayout.maxButtonHeight
|
||||
onImplicitHeightChanged: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
Component.onCompleted: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
checked: NC.UserStatus.Invisible === userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.invisibleIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Invisible")
|
||||
secondaryText: qsTr("Appear offline")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.Invisible
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
Layout.preferredHeight: topButtonsLayout.maxButtonHeight
|
||||
onImplicitHeightChanged: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
Component.onCompleted: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.topMargin: Style.standardSpacing * 2
|
||||
Layout.leftMargin: Style.standardSpacing
|
||||
Layout.rightMargin: Style.standardSpacing
|
||||
Layout.bottomMargin: Style.standardSpacing
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
font.bold: true
|
||||
text: qsTr("Status message")
|
||||
color: Style.ncTextColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: Style.standardSpacing
|
||||
Layout.leftMargin: Style.standardSpacing
|
||||
Layout.rightMargin: Style.standardSpacing
|
||||
Layout.bottomMargin: Style.standardSpacing * 2
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
spacing: 0
|
||||
ColumnLayout {
|
||||
id: statusButtonsLayout
|
||||
width: parent.width
|
||||
spacing: Style.smallSpacing
|
||||
|
||||
UserStatusSelectorButton {
|
||||
id: fieldButton
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.smallSpacing
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.bold: true
|
||||
text: qsTr("Online status")
|
||||
color: Style.ncTextColor
|
||||
}
|
||||
|
||||
Layout.preferredWidth: userStatusMessageTextField.height
|
||||
Layout.preferredHeight: userStatusMessageTextField.height
|
||||
GridLayout {
|
||||
id: topButtonsLayout
|
||||
columns: 2
|
||||
rows: 2
|
||||
columnSpacing: statusButtonsLayout.spacing
|
||||
rowSpacing: statusButtonsLayout.spacing
|
||||
|
||||
text: userStatusSelectorModel.userStatusEmoji
|
||||
|
||||
onClicked: emojiDialog.open()
|
||||
onHeightChanged: topButtonsLayout.maxButtonHeight = Math.max(topButtonsLayout.maxButtonHeight, height)
|
||||
|
||||
primary: true
|
||||
padding: 0
|
||||
z: hovered ? 2 : 0 // Make sure highlight is seen on top of text field
|
||||
|
||||
property color borderColor: showBorder ? Style.ncBlue : Style.menuBorder
|
||||
|
||||
// We create the square with only the top-left and bottom-left rounded corners
|
||||
// by overlaying different rectangles on top of each other
|
||||
background: Rectangle {
|
||||
radius: Style.slightlyRoundedButtonRadius
|
||||
color: Style.buttonBackgroundColor
|
||||
border.color: fieldButton.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: parent.width / 2
|
||||
anchors.rightMargin: -1
|
||||
z: 1
|
||||
color: Style.buttonBackgroundColor
|
||||
border.color: fieldButton.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
property int maxButtonHeight: 0
|
||||
function updateMaxButtonHeight(newHeight) {
|
||||
maxButtonHeight = Math.max(maxButtonHeight, newHeight)
|
||||
}
|
||||
|
||||
Rectangle { // We need to cover the blue border of the non-radiused rectangle
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: parent.width / 4
|
||||
anchors.rightMargin: parent.width / 4
|
||||
anchors.topMargin: Style.normalBorderWidth
|
||||
anchors.bottomMargin: Style.normalBorderWidth
|
||||
z: 2
|
||||
color: Style.buttonBackgroundColor
|
||||
UserStatusSelectorButton {
|
||||
checked: userStatusSelectorModel.onlineStatus === NC.UserStatus.Online
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.onlineIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Online")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.Online
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
checked: userStatusSelectorModel.onlineStatus === NC.UserStatus.Away
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.awayIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Away")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.Away
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
checked: userStatusSelectorModel.onlineStatus === NC.UserStatus.DoNotDisturb
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.dndIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Do not disturb")
|
||||
secondaryText: qsTr("Mute all notifications")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.DoNotDisturb
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
Layout.preferredHeight: topButtonsLayout.maxButtonHeight
|
||||
onImplicitHeightChanged: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
Component.onCompleted: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
checked: userStatusSelectorModel.onlineStatus === NC.UserStatus.Invisible
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.invisibleIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Invisible")
|
||||
secondaryText: qsTr("Appear offline")
|
||||
onClicked: userStatusSelectorModel.onlineStatus = NC.UserStatus.Invisible
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
|
||||
Layout.preferredHeight: topButtonsLayout.maxButtonHeight
|
||||
onImplicitHeightChanged: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
Component.onCompleted: topButtonsLayout.updateMaxButtonHeight(implicitHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: emojiDialog
|
||||
padding: 0
|
||||
margins: 0
|
||||
clip: true
|
||||
ColumnLayout {
|
||||
id: userStatusMessageLayout
|
||||
width: parent.width
|
||||
spacing: Style.smallSpacing
|
||||
|
||||
anchors.centerIn: Overlay.overlay
|
||||
|
||||
background: Rectangle {
|
||||
color: Style.backgroundColor
|
||||
border.width: Style.normalBorderWidth
|
||||
border.color: Style.menuBorder
|
||||
radius: Style.slightlyRoundedButtonRadius
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.smallSpacing
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.bold: true
|
||||
text: qsTr("Status message")
|
||||
color: Style.ncTextColor
|
||||
}
|
||||
|
||||
EmojiPicker {
|
||||
id: emojiPicker
|
||||
|
||||
onChosen: {
|
||||
userStatusSelectorModel.userStatusEmoji = emoji
|
||||
emojiDialog.close()
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
UserStatusSelectorButton {
|
||||
id: fieldButton
|
||||
|
||||
Layout.preferredWidth: userStatusMessageTextField.height
|
||||
Layout.preferredHeight: userStatusMessageTextField.height
|
||||
|
||||
text: userStatusSelectorModel.userStatusEmoji
|
||||
|
||||
onClicked: emojiDialog.open()
|
||||
onHeightChanged: topButtonsLayout.maxButtonHeight = Math.max(topButtonsLayout.maxButtonHeight, height)
|
||||
|
||||
primary: true
|
||||
padding: 0
|
||||
z: hovered ? 2 : 0 // Make sure highlight is seen on top of text field
|
||||
|
||||
property color borderColor: showBorder ? Style.ncBlue : Style.menuBorder
|
||||
|
||||
// We create the square with only the top-left and bottom-left rounded corners
|
||||
// by overlaying different rectangles on top of each other
|
||||
background: Rectangle {
|
||||
radius: Style.slightlyRoundedButtonRadius
|
||||
color: Style.buttonBackgroundColor
|
||||
border.color: fieldButton.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: parent.width / 2
|
||||
anchors.rightMargin: -1
|
||||
z: 1
|
||||
color: Style.buttonBackgroundColor
|
||||
border.color: fieldButton.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
}
|
||||
|
||||
Rectangle { // We need to cover the blue border of the non-radiused rectangle
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: parent.width / 4
|
||||
anchors.rightMargin: parent.width / 4
|
||||
anchors.topMargin: Style.normalBorderWidth
|
||||
anchors.bottomMargin: Style.normalBorderWidth
|
||||
z: 2
|
||||
color: Style.buttonBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: emojiDialog
|
||||
padding: 0
|
||||
margins: 0
|
||||
clip: true
|
||||
|
||||
anchors.centerIn: Overlay.overlay
|
||||
|
||||
background: Rectangle {
|
||||
color: Style.backgroundColor
|
||||
border.width: Style.normalBorderWidth
|
||||
border.color: Style.menuBorder
|
||||
radius: Style.slightlyRoundedButtonRadius
|
||||
}
|
||||
|
||||
EmojiPicker {
|
||||
id: emojiPicker
|
||||
|
||||
onChosen: {
|
||||
userStatusSelectorModel.userStatusEmoji = emoji
|
||||
emojiDialog.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userStatusMessageTextField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("What is your status?")
|
||||
placeholderTextColor: Style.ncSecondaryTextColor
|
||||
text: userStatusSelectorModel.userStatusMessage
|
||||
color: Style.ncTextColor
|
||||
selectByMouse: true
|
||||
onEditingFinished: userStatusSelectorModel.userStatusMessage = text
|
||||
|
||||
property color borderColor: activeFocus ? Style.ncBlue : Style.menuBorder
|
||||
|
||||
background: Rectangle {
|
||||
radius: Style.slightlyRoundedButtonRadius
|
||||
color: Style.backgroundColor
|
||||
border.color: userStatusMessageTextField.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.width / 2
|
||||
z: 1
|
||||
color: Style.backgroundColor
|
||||
border.color: userStatusMessageTextField.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
}
|
||||
|
||||
Rectangle { // We need to cover the blue border of the non-radiused rectangle
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: parent.width / 4
|
||||
anchors.rightMargin: parent.width / 4
|
||||
anchors.topMargin: Style.normalBorderWidth
|
||||
anchors.bottomMargin: Style.normalBorderWidth
|
||||
z: 2
|
||||
color: Style.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: userStatusSelectorModel.predefinedStatuses
|
||||
|
||||
PredefinedStatusButton {
|
||||
id: control
|
||||
Layout.fillWidth: true
|
||||
internalSpacing: Style.standardSpacing + fieldButton.padding + userStatusMessageTextField.padding
|
||||
|
||||
emoji: modelData.icon
|
||||
text: "<b>%1</b> – %2".arg(modelData.message).arg(userStatusSelectorModel.clearAtReadable(modelData))
|
||||
onClicked: userStatusSelectorModel.setPredefinedStatus(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.standardSpacing
|
||||
|
||||
Label {
|
||||
text: qsTr("Clear status message after")
|
||||
color: Style.ncTextColor
|
||||
}
|
||||
|
||||
BasicComboBox {
|
||||
id: clearComboBox
|
||||
Layout.fillWidth: true
|
||||
model: userStatusSelectorModel.clearStageTypes
|
||||
textRole: "display"
|
||||
valueRole: "clearStageType"
|
||||
displayText: userStatusSelectorModel.clearAtDisplayString
|
||||
onActivated: userStatusSelectorModel.setClearAt(currentValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userStatusMessageTextField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("What is your status?")
|
||||
placeholderTextColor: Style.ncSecondaryTextColor
|
||||
text: userStatusSelectorModel.userStatusMessage
|
||||
color: Style.ncTextColor
|
||||
selectByMouse: true
|
||||
onEditingFinished: userStatusSelectorModel.userStatusMessage = text
|
||||
ErrorBox {
|
||||
width: parent.width
|
||||
|
||||
property color borderColor: activeFocus ? Style.ncBlue : Style.menuBorder
|
||||
|
||||
background: Rectangle {
|
||||
radius: Style.slightlyRoundedButtonRadius
|
||||
color: Style.backgroundColor
|
||||
border.color: userStatusMessageTextField.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.width / 2
|
||||
z: 1
|
||||
color: Style.backgroundColor
|
||||
border.color: userStatusMessageTextField.borderColor
|
||||
border.width: Style.normalBorderWidth
|
||||
}
|
||||
|
||||
Rectangle { // We need to cover the blue border of the non-radiused rectangle
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: parent.width / 4
|
||||
anchors.rightMargin: parent.width / 4
|
||||
anchors.topMargin: Style.normalBorderWidth
|
||||
anchors.bottomMargin: Style.normalBorderWidth
|
||||
z: 2
|
||||
color: Style.backgroundColor
|
||||
}
|
||||
}
|
||||
visible: userStatusSelectorModel.errorMessage != ""
|
||||
text: "<b>Error:</b> " + userStatusSelectorModel.errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: userStatusSelectorModel.predefinedStatuses
|
||||
|
||||
PredefinedStatusButton {
|
||||
id: control
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.standardSpacing
|
||||
Layout.rightMargin: Style.standardSpacing
|
||||
internalSpacing: Style.standardSpacing + fieldButton.padding + userStatusMessageTextField.padding
|
||||
|
||||
emoji: modelData.icon
|
||||
text: "<b>%1</b> – %2".arg(modelData.message).arg(userStatusSelectorModel.clearAtReadable(modelData))
|
||||
onClicked: userStatusSelectorModel.setPredefinedStatus(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: Style.standardSpacing * 2
|
||||
Layout.leftMargin: Style.standardSpacing
|
||||
Layout.rightMargin: Style.standardSpacing
|
||||
Layout.bottomMargin: Style.standardSpacing
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: Style.standardSpacing
|
||||
|
||||
Label {
|
||||
text: qsTr("Clear status message after")
|
||||
color: Style.ncTextColor
|
||||
}
|
||||
|
||||
BasicComboBox {
|
||||
id: clearComboBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: userStatusSelectorModel.clearStageTypes
|
||||
textRole: "display"
|
||||
valueRole: "clearStageType"
|
||||
displayText: userStatusSelectorModel.clearAtDisplayString
|
||||
onActivated: userStatusSelectorModel.setClearAt(currentValue)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: Style.standardSpacing
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
UserStatusSelectorButton {
|
||||
Layout.fillWidth: true
|
||||
primary: true
|
||||
text: qsTr("Cancel")
|
||||
onClicked: finished()
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
Layout.fillWidth: true
|
||||
primary: true
|
||||
|
@ -298,19 +321,11 @@ ColumnLayout {
|
|||
onClicked: userStatusSelectorModel.clearUserStatus()
|
||||
}
|
||||
UserStatusSelectorButton {
|
||||
Layout.fillWidth: true
|
||||
primary: true
|
||||
colored: true
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Set status message")
|
||||
onClicked: userStatusSelectorModel.setUserStatus()
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBox {
|
||||
Layout.margins: Style.standardSpacing
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: userStatusSelectorModel.errorMessage != ""
|
||||
text: "<b>Error:</b> " + userStatusSelectorModel.errorMessage
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import QtQuick.Window 2.15
|
||||
import Style 1.0
|
||||
|
||||
import com.nextcloud.desktopclient 1.0 as NC
|
||||
|
||||
Window {
|
||||
id: dialog
|
||||
|
||||
title: qsTr("Set account status")
|
||||
color: Style.backgroundColor
|
||||
|
||||
property NC.UserStatusSelectorModel model: NC.UserStatusSelectorModel {
|
||||
onFinished: dialog.close()
|
||||
}
|
||||
property int userIndex
|
||||
onUserIndexChanged: model.load(userIndex)
|
||||
|
||||
minimumWidth: view.implicitWidth
|
||||
minimumHeight: view.implicitHeight
|
||||
maximumWidth: view.implicitWidth
|
||||
maximumHeight: view.implicitHeight
|
||||
width: maximumWidth
|
||||
height: maximumHeight
|
||||
|
||||
visible: true
|
||||
|
||||
flags: Qt.Dialog
|
||||
|
||||
UserStatusSelector {
|
||||
id: view
|
||||
userStatusSelectorModel: model
|
||||
}
|
||||
}
|
45
src/gui/UserStatusSelectorPage.qml
Normal file
45
src/gui/UserStatusSelectorPage.qml
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import Style 1.0
|
||||
|
||||
import com.nextcloud.desktopclient 1.0 as NC
|
||||
|
||||
Page {
|
||||
id: page
|
||||
|
||||
signal finished
|
||||
|
||||
property int userIndex: -1
|
||||
property NC.UserStatusSelectorModel model: NC.UserStatusSelectorModel {
|
||||
userIndex: page.userIndex
|
||||
onFinished: page.finished()
|
||||
}
|
||||
|
||||
padding: Style.standardSpacing * 2
|
||||
|
||||
background: Rectangle {
|
||||
color: Style.backgroundColor
|
||||
radius: Style.trayWindowRadius
|
||||
}
|
||||
|
||||
contentItem: UserStatusSelector {
|
||||
id: userStatusSelector
|
||||
userStatusSelectorModel: model
|
||||
onImplicitHeightChanged: implicitHeight > page.availableHeight ?
|
||||
spacing = Style.standardSpacing : spacing = Style.standardSpacing * 2
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ MenuItem {
|
|||
property variant comp;
|
||||
activeFocusOnTab: false
|
||||
|
||||
signal showUserStatusSelectorDialog(int id)
|
||||
signal showUserStatusSelector(int id)
|
||||
|
||||
RowLayout {
|
||||
id: userLineLayout
|
||||
|
@ -183,7 +183,7 @@ MenuItem {
|
|||
font.pixelSize: Style.topLinePixelSize
|
||||
palette.windowText: Style.ncTextColor
|
||||
hoverEnabled: true
|
||||
onClicked: showUserStatusSelectorDialog(index)
|
||||
onClicked: showUserStatusSelector(index)
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import QtQml 2.12
|
||||
import QtQml.Models 2.1
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
|
@ -13,7 +11,7 @@ import Style 1.0
|
|||
|
||||
import com.nextcloud.desktopclient 1.0
|
||||
|
||||
Window {
|
||||
ApplicationWindow {
|
||||
id: trayWindow
|
||||
|
||||
title: Systray.windowTitle
|
||||
|
@ -53,6 +51,13 @@ Window {
|
|||
syncStatus.model.load();
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius
|
||||
border.width: Style.trayWindowBorderWidth
|
||||
border.color: Style.menuBorder
|
||||
color: Style.backgroundColor
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: UserModel
|
||||
function onCurrentUserChanged() {
|
||||
|
@ -78,6 +83,8 @@ Window {
|
|||
target: Systray
|
||||
|
||||
function onIsOpenChanged() {
|
||||
userStatusDrawer.close()
|
||||
|
||||
if(Systray.isOpen) {
|
||||
accountMenu.close();
|
||||
appsMenu.close();
|
||||
|
@ -98,18 +105,54 @@ Window {
|
|||
OpacityMask {
|
||||
anchors.fill: parent
|
||||
source: ShaderEffectSource {
|
||||
sourceItem: trayWindowBackground
|
||||
sourceItem: trayWindowMainItem
|
||||
hideSource: true
|
||||
}
|
||||
maskSource: Rectangle {
|
||||
width: trayWindowBackground.width
|
||||
height: trayWindowBackground.height
|
||||
width: trayWindow.width
|
||||
height: trayWindow.height
|
||||
radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: trayWindowBackground
|
||||
Drawer {
|
||||
id: userStatusDrawer
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
padding: 0
|
||||
edge: Qt.BottomEdge
|
||||
modal: false
|
||||
visible: false
|
||||
|
||||
background: Rectangle {
|
||||
radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius
|
||||
border.width: Style.trayWindowBorderWidth
|
||||
border.color: Style.menuBorder
|
||||
color: Style.backgroundColor
|
||||
}
|
||||
|
||||
property int userIndex: 0
|
||||
|
||||
function openUserStatusDrawer(index) {
|
||||
console.log(`About to show dialog for user with index ${index}`);
|
||||
userIndex = index;
|
||||
open();
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: userStatusContents
|
||||
anchors.fill: parent
|
||||
active: userStatusDrawer.visible
|
||||
sourceComponent: UserStatusSelectorPage {
|
||||
anchors.fill: parent
|
||||
userIndex: userStatusDrawer.userIndex
|
||||
onFinished: userStatusDrawer.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: trayWindowMainItem
|
||||
|
||||
property bool isUnifiedSearchActive: unifiedSearchResultsListViewSkeletonLoader.active
|
||||
|| unifiedSearchResultNothingFound.visible
|
||||
|
@ -117,10 +160,7 @@ Window {
|
|||
|| unifiedSearchResultsListView.visible
|
||||
|
||||
anchors.fill: parent
|
||||
radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius
|
||||
border.width: Style.trayWindowBorderWidth
|
||||
border.color: Style.menuBorder
|
||||
color: Style.backgroundColor
|
||||
clip: true
|
||||
|
||||
Accessible.role: Accessible.Grouping
|
||||
Accessible.name: qsTr("Nextcloud desktop main dialog")
|
||||
|
@ -128,9 +168,9 @@ Window {
|
|||
Rectangle {
|
||||
id: trayWindowHeaderBackground
|
||||
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.top: trayWindowBackground.top
|
||||
anchors.left: trayWindowMainItem.left
|
||||
anchors.right: trayWindowMainItem.right
|
||||
anchors.top: trayWindowMainItem.top
|
||||
height: Style.trayWindowHeaderHeight
|
||||
color: UserModel.currentUser.headerColor
|
||||
|
||||
|
@ -206,35 +246,12 @@ Window {
|
|||
userLineInstantiator.active = true;
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: userStatusSelectorDialogLoader
|
||||
|
||||
property int userIndex
|
||||
|
||||
function openDialog(newUserIndex) {
|
||||
console.log(`About to show dialog for user with index ${newUserIndex}`);
|
||||
userIndex = newUserIndex;
|
||||
active = true;
|
||||
item.show();
|
||||
}
|
||||
|
||||
active: false
|
||||
sourceComponent: UserStatusSelectorDialog {
|
||||
userIndex: userStatusSelectorDialogLoader.userIndex
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
item.model.load(userIndex);
|
||||
item.show();
|
||||
}
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
id: userLineInstantiator
|
||||
model: UserModel
|
||||
delegate: UserLine {
|
||||
onShowUserStatusSelectorDialog: {
|
||||
userStatusSelectorDialogLoader.openDialog(model.index);
|
||||
onShowUserStatusSelector: {
|
||||
userStatusDrawer.openUserStatusDrawer(model.index);
|
||||
accountMenu.close();
|
||||
}
|
||||
}
|
||||
|
@ -661,8 +678,8 @@ Window {
|
|||
|
||||
anchors {
|
||||
top: trayWindowHeaderBackground.bottom
|
||||
left: trayWindowBackground.left
|
||||
right: trayWindowBackground.right
|
||||
left: trayWindowMainItem.left
|
||||
right: trayWindowMainItem.right
|
||||
|
||||
topMargin: Style.trayHorizontalMargin + controlRoot.padding
|
||||
leftMargin: Style.trayHorizontalMargin + controlRoot.padding
|
||||
|
@ -681,8 +698,8 @@ Window {
|
|||
visible: UserModel.currentUser.unifiedSearchResultsListModel.errorString && !unifiedSearchResultsListView.visible && ! UserModel.currentUser.unifiedSearchResultsListModel.isSearchInProgress && ! UserModel.currentUser.unifiedSearchResultsListModel.currentFetchMoreInProgressProviderId
|
||||
text: UserModel.currentUser.unifiedSearchResultsListModel.errorString
|
||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.left: trayWindowMainItem.left
|
||||
anchors.right: trayWindowMainItem.right
|
||||
anchors.margins: Style.trayHorizontalMargin
|
||||
}
|
||||
|
||||
|
@ -690,8 +707,8 @@ Window {
|
|||
id: unifiedSearchResultNothingFound
|
||||
visible: false
|
||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.left: trayWindowMainItem.left
|
||||
anchors.right: trayWindowMainItem.right
|
||||
anchors.topMargin: Style.trayHorizontalMargin
|
||||
|
||||
text: UserModel.currentUser.unifiedSearchResultsListModel.searchTerm
|
||||
|
@ -724,9 +741,9 @@ Window {
|
|||
Loader {
|
||||
id: unifiedSearchResultsListViewSkeletonLoader
|
||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.bottom: trayWindowBackground.bottom
|
||||
anchors.left: trayWindowMainItem.left
|
||||
anchors.right: trayWindowMainItem.right
|
||||
anchors.bottom: trayWindowMainItem.bottom
|
||||
|
||||
active: !unifiedSearchResultNothingFound.visible &&
|
||||
!unifiedSearchResultsListView.visible &&
|
||||
|
@ -752,9 +769,9 @@ Window {
|
|||
visible: unifiedSearchResultsListView.count > 0
|
||||
|
||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.bottom: trayWindowBackground.bottom
|
||||
anchors.left: trayWindowMainItem.left
|
||||
anchors.right: trayWindowMainItem.right
|
||||
anchors.bottom: trayWindowMainItem.bottom
|
||||
|
||||
ListView {
|
||||
id: unifiedSearchResultsListView
|
||||
|
@ -791,19 +808,19 @@ Window {
|
|||
SyncStatus {
|
||||
id: syncStatus
|
||||
|
||||
visible: !trayWindowBackground.isUnifiedSearchActive
|
||||
visible: !trayWindowMainItem.isUnifiedSearchActive
|
||||
|
||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.left: trayWindowMainItem.left
|
||||
anchors.right: trayWindowMainItem.right
|
||||
}
|
||||
|
||||
ActivityList {
|
||||
visible: !trayWindowBackground.isUnifiedSearchActive
|
||||
visible: !trayWindowMainItem.isUnifiedSearchActive
|
||||
anchors.top: syncStatus.bottom
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.bottom: trayWindowBackground.bottom
|
||||
anchors.left: trayWindowMainItem.left
|
||||
anchors.right: trayWindowMainItem.right
|
||||
anchors.bottom: trayWindowMainItem.bottom
|
||||
|
||||
activeFocusOnTab: true
|
||||
model: activityModel
|
||||
|
@ -833,5 +850,5 @@ Window {
|
|||
|
||||
onLoaded: refresh()
|
||||
}
|
||||
} // Rectangle trayWindowBackground
|
||||
} // Item trayWindowMainItem
|
||||
}
|
||||
|
|
|
@ -74,11 +74,26 @@ UserStatusSelectorModel::UserStatusSelectorModel(const UserStatus &userStatus,
|
|||
_userStatus.setIcon("😀");
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::load(int id)
|
||||
int UserStatusSelectorModel::userIndex() const
|
||||
{
|
||||
return _userIndex;
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setUserIndex(const int userIndex)
|
||||
{
|
||||
if(userIndex < 0) {
|
||||
qCWarning(lcUserStatusDialogModel) << "Invalid user index: " << _userIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
reset();
|
||||
qCDebug(lcUserStatusDialogModel) << "Loading user status connector for user with index: " << id;
|
||||
_userStatusConnector = UserModel::instance()->userStatusConnector(id);
|
||||
|
||||
_userIndex = userIndex;
|
||||
emit userIndexChanged();
|
||||
|
||||
qCDebug(lcUserStatusDialogModel) << "Loading user status connector for user with index: " << _userIndex;
|
||||
_userStatusConnector = UserModel::instance()->userStatusConnector(_userIndex);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
|
@ -102,6 +117,7 @@ void UserStatusSelectorModel::reset()
|
|||
void UserStatusSelectorModel::init()
|
||||
{
|
||||
if (!_userStatusConnector) {
|
||||
qCWarning(lcUserStatusDialogModel) << "No user status conenctor set";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -182,7 +198,7 @@ void UserStatusSelectorModel::setOnlineStatus(UserStatus::OnlineStatus status)
|
|||
|
||||
_userStatus.setState(status);
|
||||
_userStatusConnector->setUserStatus(_userStatus);
|
||||
emit onlineStatusChanged();
|
||||
emit userStatusChanged();
|
||||
}
|
||||
|
||||
QUrl UserStatusSelectorModel::onlineIcon() const
|
||||
|
@ -234,9 +250,7 @@ QString UserStatusSelectorModel::userStatusEmoji() const
|
|||
|
||||
void UserStatusSelectorModel::onUserStatusFetched(const UserStatus &userStatus)
|
||||
{
|
||||
if (userStatus.state() != UserStatus::OnlineStatus::Offline) {
|
||||
_userStatus.setState(userStatus.state());
|
||||
}
|
||||
_userStatus.setState(userStatus.state());
|
||||
_userStatus.setMessage(userStatus.message());
|
||||
_userStatus.setMessagePredefined(userStatus.messagePredefined());
|
||||
_userStatus.setId(userStatus.id());
|
||||
|
@ -247,7 +261,6 @@ void UserStatusSelectorModel::onUserStatusFetched(const UserStatus &userStatus)
|
|||
}
|
||||
|
||||
emit userStatusChanged();
|
||||
emit onlineStatusChanged();
|
||||
emit clearAtDisplayStringChanged();
|
||||
}
|
||||
|
||||
|
|
|
@ -34,9 +34,10 @@ class UserStatusSelectorModel : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int userIndex READ userIndex WRITE setUserIndex NOTIFY userIndexChanged)
|
||||
Q_PROPERTY(QString userStatusMessage READ userStatusMessage WRITE setUserStatusMessage NOTIFY userStatusChanged)
|
||||
Q_PROPERTY(QString userStatusEmoji READ userStatusEmoji WRITE setUserStatusEmoji NOTIFY userStatusChanged)
|
||||
Q_PROPERTY(OCC::UserStatus::OnlineStatus onlineStatus READ onlineStatus WRITE setOnlineStatus NOTIFY onlineStatusChanged)
|
||||
Q_PROPERTY(OCC::UserStatus::OnlineStatus onlineStatus READ onlineStatus WRITE setOnlineStatus NOTIFY userStatusChanged)
|
||||
Q_PROPERTY(QVector<OCC::UserStatus> predefinedStatuses READ predefinedStatuses NOTIFY predefinedStatusesChanged)
|
||||
Q_PROPERTY(QVariantList clearStageTypes READ clearStageTypes CONSTANT)
|
||||
Q_PROPERTY(QString clearAtDisplayString READ clearAtDisplayString NOTIFY clearAtDisplayStringChanged)
|
||||
|
@ -73,6 +74,8 @@ public:
|
|||
explicit UserStatusSelectorModel(const UserStatus &userStatus,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_REQUIRED_RESULT int userIndex() const;
|
||||
|
||||
Q_REQUIRED_RESULT UserStatus::OnlineStatus onlineStatus() const;
|
||||
void setOnlineStatus(UserStatus::OnlineStatus status);
|
||||
|
||||
|
@ -95,16 +98,16 @@ public:
|
|||
Q_REQUIRED_RESULT QString errorMessage() const;
|
||||
|
||||
public slots:
|
||||
void load(int id);
|
||||
void setUserIndex(const int userIndex);
|
||||
void setUserStatus();
|
||||
void clearUserStatus();
|
||||
void setClearAt(const ClearStageType clearStageType);
|
||||
void setPredefinedStatus(const UserStatus &predefinedStatus);
|
||||
|
||||
signals:
|
||||
void userIndexChanged();
|
||||
void errorMessageChanged();
|
||||
void userStatusChanged();
|
||||
void onlineStatusChanged();
|
||||
void clearAtDisplayStringChanged();
|
||||
void predefinedStatusesChanged();
|
||||
void finished();
|
||||
|
@ -125,6 +128,7 @@ private:
|
|||
void setError(const QString &reason);
|
||||
void clearError();
|
||||
|
||||
int _userIndex = -1;
|
||||
std::shared_ptr<UserStatusConnector> _userStatusConnector {};
|
||||
QVector<UserStatus> _predefinedStatuses;
|
||||
UserStatus _userStatus;
|
||||
|
|
|
@ -252,23 +252,23 @@ private slots:
|
|||
OCC::UserStatus::OnlineStatus::Offline, false, {} });
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
QCOMPARE(model.onlineStatus(), OCC::UserStatus::OnlineStatus::Online);
|
||||
QCOMPARE(model.onlineStatus(), OCC::UserStatus::OnlineStatus::Offline);
|
||||
QCOMPARE(model.userStatusMessage(), "");
|
||||
QCOMPARE(model.userStatusEmoji(), "😀");
|
||||
QCOMPARE(model.clearAtDisplayString(), tr("Don't clear"));
|
||||
}
|
||||
|
||||
void testSetOnlineStatus_emitOnlineStatusChanged()
|
||||
void testSetOnlineStatus_emiUserStatusChanged()
|
||||
{
|
||||
const OCC::UserStatus::OnlineStatus onlineStatus(OCC::UserStatus::OnlineStatus::Invisible);
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy onlineStatusChangedSpy(&model,
|
||||
&OCC::UserStatusSelectorModel::onlineStatusChanged);
|
||||
QSignalSpy userStatusChangedSpy(&model,
|
||||
&OCC::UserStatusSelectorModel::userStatusChanged);
|
||||
|
||||
model.setOnlineStatus(onlineStatus);
|
||||
|
||||
QCOMPARE(onlineStatusChangedSpy.count(), 1);
|
||||
QCOMPARE(userStatusChangedSpy.count(), 1);
|
||||
}
|
||||
|
||||
void testSetUserStatus_setCustomMessage_userStatusSetCorrect()
|
||||
|
|
|
@ -35,6 +35,8 @@ QtObject {
|
|||
property int trayWindowBorderWidth: variableSize(1)
|
||||
property int trayWindowHeaderHeight: variableSize(60)
|
||||
property int trayHorizontalMargin: 10
|
||||
property int trayModalWidth: 380
|
||||
property int trayModalHeight: 490
|
||||
property int trayListItemIconSize: accountAvatarSize
|
||||
property real thumbnailImageSizeReduction: 0.2 // We reserve some space within the thumbnail "item", here about 20%.
|
||||
// This is because we need to also add the added/modified icon and we
|
||||
|
@ -43,6 +45,7 @@ QtObject {
|
|||
// images, which will work so long as the thumbnails are left aligned
|
||||
|
||||
property int standardSpacing: 10
|
||||
property int smallSpacing: 5
|
||||
|
||||
property int minActivityHeight: variableSize(40)
|
||||
|
||||
|
|
Loading…
Reference in a new issue