From 2fe4bc54030c44589a48a043c4f93bf36b9d29a2 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 10 Aug 2022 17:45:11 +0200 Subject: [PATCH] Add a ScrollView to the predefined statuses area of the UserStatusSelector Signed-off-by: Claudio Cambra --- src/gui/UserStatusSelector.qml | 533 ++++++++++++++++----------------- 1 file changed, 261 insertions(+), 272 deletions(-) diff --git a/src/gui/UserStatusSelector.qml b/src/gui/UserStatusSelector.qml index eebcee586..f7ec75027 100644 --- a/src/gui/UserStatusSelector.qml +++ b/src/gui/UserStatusSelector.qml @@ -17,8 +17,8 @@ import QtQuick.Dialogs 1.3 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 -import com.nextcloud.desktopclient 1.0 as NC +import com.nextcloud.desktopclient 1.0 as NC import Style 1.0 ColumnLayout { @@ -26,299 +26,288 @@ ColumnLayout { spacing: Style.standardSpacing * 2 property NC.UserStatusSelectorModel userStatusSelectorModel - 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. - - id: mainContentsLayout - spacing: rootLayout.spacing + ColumnLayout { + id: statusButtonsLayout Layout.fillWidth: true - Layout.fillHeight: true - Layout.alignment: Qt.AlignTop + spacing: Style.smallSpacing - ColumnLayout { - id: statusButtonsLayout - width: parent.width - spacing: Style.smallSpacing - - Label { - Layout.fillWidth: true - Layout.bottomMargin: Style.smallSpacing - horizontalAlignment: Text.AlignHCenter - font.bold: true - text: qsTr("Online status") - color: Style.ncTextColor - } - - GridLayout { - id: topButtonsLayout - columns: 2 - rows: 2 - columnSpacing: statusButtonsLayout.spacing - rowSpacing: statusButtonsLayout.spacing - - property int maxButtonHeight: 0 - function updateMaxButtonHeight(newHeight) { - maxButtonHeight = Math.max(maxButtonHeight, newHeight) - } - - 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) - } - } + Label { + Layout.fillWidth: true + Layout.bottomMargin: Style.smallSpacing + horizontalAlignment: Text.AlignHCenter + font.bold: true + text: qsTr("Online status") + color: Style.ncTextColor } - ColumnLayout { - id: userStatusMessageLayout - width: parent.width - spacing: Style.smallSpacing + GridLayout { + id: topButtonsLayout + columns: 2 + rows: 2 + columnSpacing: statusButtonsLayout.spacing + rowSpacing: statusButtonsLayout.spacing - Label { - Layout.fillWidth: true - Layout.bottomMargin: Style.smallSpacing - horizontalAlignment: Text.AlignHCenter - font.bold: true - text: qsTr("Status message") - color: Style.ncTextColor + property int maxButtonHeight: 0 + function updateMaxButtonHeight(newHeight) { + maxButtonHeight = Math.max(maxButtonHeight, newHeight) } - RowLayout { - id: statusFieldLayout + 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 - 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 - } - } - } + 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 - ColumnLayout { Layout.fillWidth: true - spacing: 0 + implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width - Repeater { - model: userStatusSelectorModel.predefinedStatuses - - PredefinedStatusButton { - Layout.fillWidth: true - - leftPadding: 0 - emojiWidth: fieldButton.width - internalSpacing: statusFieldLayout.spacing + userStatusMessageTextField.leftPadding - - emoji: modelData.icon - text: "%1 – %2".arg(modelData.message).arg(userStatusSelectorModel.clearAtReadable(modelData)) - onClicked: userStatusSelectorModel.setPredefinedStatus(modelData) - } - } } + 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 - RowLayout { Layout.fillWidth: true - spacing: Style.smallSpacing - - Label { - id: clearComboLabel - - Layout.fillWidth: true - Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - - text: qsTr("Clear status message after") - color: Style.ncTextColor - wrapMode: Text.Wrap - } - - BasicComboBox { - id: clearComboBox - - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumWidth: implicitWidth - - model: userStatusSelectorModel.clearStageTypes - textRole: "display" - valueRole: "clearStageType" - displayText: userStatusSelectorModel.clearAtDisplayString - onActivated: userStatusSelectorModel.setClearAt(currentValue) - } + 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 - ErrorBox { - width: parent.width - - visible: userStatusSelectorModel.errorMessage != "" - text: "Error: " + userStatusSelectorModel.errorMessage + 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) + } } } + ColumnLayout { + id: userStatusMessageLayout + + Layout.fillWidth: true + Layout.fillHeight: true + spacing: Style.smallSpacing + + Label { + Layout.fillWidth: true + Layout.bottomMargin: Style.smallSpacing + horizontalAlignment: Text.AlignHCenter + font.bold: true + text: qsTr("Status message") + color: Style.ncTextColor + } + + RowLayout { + id: statusFieldLayout + 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 + } + } + } + } + + ScrollView { + id: predefinedStatusesScrollView + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + ListView { + spacing: 0 + model: userStatusSelectorModel.predefinedStatuses + delegate: PredefinedStatusButton { + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + emojiWidth: fieldButton.width + internalSpacing: statusFieldLayout.spacing + userStatusMessageTextField.leftPadding + + emoji: modelData.icon + text: "%1 – %2".arg(modelData.message).arg(userStatusSelectorModel.clearAtReadable(modelData)) + onClicked: userStatusSelectorModel.setPredefinedStatus(modelData) + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.smallSpacing + + Label { + id: clearComboLabel + + Layout.fillWidth: true + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + + text: qsTr("Clear status message after") + color: Style.ncTextColor + wrapMode: Text.Wrap + } + + BasicComboBox { + id: clearComboBox + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumWidth: implicitWidth + + model: userStatusSelectorModel.clearStageTypes + textRole: "display" + valueRole: "clearStageType" + displayText: userStatusSelectorModel.clearAtDisplayString + onActivated: userStatusSelectorModel.setClearAt(currentValue) + } + } + } + + ErrorBox { + width: parent.width + + visible: userStatusSelectorModel.errorMessage != "" + text: "Error: " + userStatusSelectorModel.errorMessage + } + RowLayout { + id: bottomButtonBox Layout.fillWidth: true Layout.alignment: Qt.AlignBottom