Make UserStatusSelector a dismissible page pushed onto the tray window
authorClaudio Cambra <claudio.cambra@gmail.com>
Wed, 20 Jul 2022 17:56:57 +0000 (19:56 +0200)
committerCamila (Rebase PR Action) <hello@camila.codes>
Wed, 10 Aug 2022 09:22:33 +0000 (09:22 +0000)
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
resources.qrc
src/gui/UserStatusSelector.qml
src/gui/UserStatusSelectorDialog.qml [deleted file]
src/gui/UserStatusSelectorPage.qml [new file with mode: 0644]
src/gui/tray/UserLine.qml
src/gui/tray/Window.qml
src/gui/userstatusselectormodel.cpp
src/gui/userstatusselectormodel.h
test/testsetuserstatusdialog.cpp
theme/Style/Style.qml

index 6aeb159b36535fb07e16a9190e878c8380bad962..d9db7bc05d87c64046d259aa256dca5fdd685b8e 100644 (file)
@@ -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>
index f480597cb47c65b33d317c906ae8027c91c7b660..68809ae4bd907b170d3e5f9d1e07fb06ab4956c6 100644 (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
-
-        Layout.margins: Style.standardSpacing
-        Layout.alignment: Qt.AlignTop
-        columns: 2
-        rows: 2
-        columnSpacing: Style.standardSpacing
-        rowSpacing: Style.standardSpacing
-
-        property int maxButtonHeight: 0
-        function updateMaxButtonHeight(newHeight) {
-            maxButtonHeight = Math.max(maxButtonHeight, newHeight)
-        }
+    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.
 
-        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
+        id: mainContentsLayout
+        spacing: rootLayout.spacing
 
-            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
+        Layout.fillHeight: true
+        Layout.alignment: Qt.AlignTop
 
-            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
+        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
+            }
 
-            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)
-        }
-    }
+            GridLayout {
+                id: topButtonsLayout
+                columns: 2
+                rows: 2
+                columnSpacing: statusButtonsLayout.spacing
+                rowSpacing: statusButtonsLayout.spacing
 
-    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
-    }
+                property int maxButtonHeight: 0
+                function updateMaxButtonHeight(newHeight) {
+                    maxButtonHeight = Math.max(maxButtonHeight, newHeight)
+                }
 
-    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
+                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
 
-        spacing: 0
+                    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
 
-        UserStatusSelectorButton {
-            id: fieldButton
+                    Layout.fillWidth: true
+                    implicitWidth: 200 // Pretty much a hack to ensure all the buttons are equal in width
 
-            Layout.preferredWidth: userStatusMessageTextField.height 
-            Layout.preferredHeight: userStatusMessageTextField.height
+                }
+                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)
+                }
+            }
+        }
 
-            text: userStatusSelectorModel.userStatusEmoji
+        ColumnLayout {
+            id: userStatusMessageLayout
+            width: parent.width
+            spacing: Style.smallSpacing
+
+            Label {
+                Layout.fillWidth: true
+                Layout.bottomMargin: Style.smallSpacing
+                horizontalAlignment: Text.AlignHCenter
+                font.bold: true
+                text: qsTr("Status message")
+                color: Style.ncTextColor
+            }
 
-            onClicked: emojiDialog.open()
-            onHeightChanged: topButtonsLayout.maxButtonHeight = Math.max(topButtonsLayout.maxButtonHeight, height)
+            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
+                        }
+                    }
+                }
 
-            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
+                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()
+                        }
+                    }
                 }
 
-                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
+                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
+                        }
+                    }
                 }
             }
-        }
 
-        Popup {
-            id: emojiDialog
-            padding: 0
-            margins: 0
-            clip: true
+            ColumnLayout {
+                Layout.fillWidth: true
+                spacing: 0
 
-            anchors.centerIn: Overlay.overlay
+                Repeater {
+                    model: userStatusSelectorModel.predefinedStatuses
 
-            background: Rectangle {
-                color: Style.backgroundColor
-                border.width: Style.normalBorderWidth
-                border.color: Style.menuBorder
-                radius: Style.slightlyRoundedButtonRadius
-            }
-            
-            EmojiPicker {
-                id: emojiPicker
+                    PredefinedStatusButton {
+                        id: control
+                        Layout.fillWidth: true
+                        internalSpacing: Style.standardSpacing + fieldButton.padding + userStatusMessageTextField.padding
 
-                onChosen: {
-                    userStatusSelectorModel.userStatusEmoji = emoji
-                    emojiDialog.close()
+                        emoji: modelData.icon
+                        text: "<b>%1</b> â€“ %2".arg(modelData.message).arg(userStatusSelectorModel.clearAtReadable(modelData))
+                        onClicked: userStatusSelectorModel.setPredefinedStatus(modelData)
+                    }
                 }
             }
-        }
 
-        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
+            RowLayout {
+                Layout.fillWidth: true
+                spacing: Style.standardSpacing
+
+                Label {
+                    text: qsTr("Clear status message after")
+                    color: Style.ncTextColor
                 }
 
-                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
+                BasicComboBox {
+                    id: clearComboBox
+                    Layout.fillWidth: true
+                    model: userStatusSelectorModel.clearStageTypes
+                    textRole: "display"
+                    valueRole: "clearStageType"
+                    displayText: userStatusSelectorModel.clearAtDisplayString
+                    onActivated: userStatusSelectorModel.setClearAt(currentValue)
                 }
             }
         }
-    }
-
-    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
+        ErrorBox {
+            width: parent.width
 
-            emoji: modelData.icon
-            text: "<b>%1</b> â€“ %2".arg(modelData.message).arg(userStatusSelectorModel.clearAtReadable(modelData))
-            onClicked: userStatusSelectorModel.setPredefinedStatus(modelData)
+            visible: userStatusSelectorModel.errorMessage != ""
+            text: "<b>Error:</b> " + userStatusSelectorModel.errorMessage
         }
     }
 
-   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
-    }
 }
diff --git a/src/gui/UserStatusSelectorDialog.qml b/src/gui/UserStatusSelectorDialog.qml
deleted file mode 100644 (file)
index 938eae5..0000000
+++ /dev/null
@@ -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
-    }
-}
diff --git a/src/gui/UserStatusSelectorPage.qml b/src/gui/UserStatusSelectorPage.qml
new file mode 100644 (file)
index 0000000..c89d821
--- /dev/null
@@ -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
+    }
+}
index e3d88888d8d08dd215de5a3b1b668cf42c57dafa..4429aaba9d0ea60fa2903fe80689c917609ed33f 100644 (file)
@@ -18,7 +18,7 @@ MenuItem {
     property variant comp;\r
     activeFocusOnTab: false\r
 \r
-    signal showUserStatusSelectorDialog(int id)\r
+    signal showUserStatusSelector(int id)\r
 \r
     RowLayout {\r
         id: userLineLayout\r
@@ -183,7 +183,7 @@ MenuItem {
                     font.pixelSize: Style.topLinePixelSize\r
                     palette.windowText: Style.ncTextColor\r
                     hoverEnabled: true\r
-                    onClicked: showUserStatusSelectorDialog(index)\r
+                    onClicked: showUserStatusSelector(index)\r
 \r
                     background: Item {\r
                         height: parent.height\r
index 4d9e531bc32cd4d70fdf0577698d47cc61d7ef37..bc629d8ee3857c405a37e5759a6ab3922539ce48 100644 (file)
@@ -1,5 +1,3 @@
-import QtQml 2.12\r
-import QtQml.Models 2.1\r
 import QtQuick 2.15\r
 import QtQuick.Window 2.3\r
 import QtQuick.Controls 2.3\r
@@ -13,7 +11,7 @@ import Style 1.0
 \r
 import com.nextcloud.desktopclient 1.0\r
 \r
-Window {\r
+ApplicationWindow {\r
     id:         trayWindow\r
 \r
     title:      Systray.windowTitle\r
@@ -53,6 +51,13 @@ Window {
         syncStatus.model.load();\r
     }\r
 \r
+    background: Rectangle {\r
+        radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius\r
+        border.width: Style.trayWindowBorderWidth\r
+        border.color: Style.menuBorder\r
+        color: Style.backgroundColor\r
+    }\r
+\r
     Connections {\r
         target: UserModel\r
         function onCurrentUserChanged() {\r
@@ -78,6 +83,8 @@ Window {
         target: Systray\r
 \r
         function onIsOpenChanged() {\r
+            userStatusDrawer.close()\r
+\r
             if(Systray.isOpen) {\r
                 accountMenu.close();\r
                 appsMenu.close();\r
@@ -98,18 +105,54 @@ Window {
     OpacityMask {\r
         anchors.fill: parent\r
         source: ShaderEffectSource {\r
-            sourceItem: trayWindowBackground\r
+            sourceItem: trayWindowMainItem\r
             hideSource: true\r
         }\r
         maskSource: Rectangle {\r
-            width: trayWindowBackground.width\r
-            height: trayWindowBackground.height\r
+            width: trayWindow.width\r
+            height: trayWindow.height\r
             radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius\r
         }\r
     }\r
 \r
-    Rectangle {\r
-        id: trayWindowBackground\r
+    Drawer {\r
+        id: userStatusDrawer\r
+        width: parent.width\r
+        height: parent.height\r
+        padding: 0\r
+        edge: Qt.BottomEdge\r
+        modal: false\r
+        visible: false\r
+\r
+        background: Rectangle {\r
+            radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius\r
+            border.width: Style.trayWindowBorderWidth\r
+            border.color: Style.menuBorder\r
+            color: Style.backgroundColor\r
+        }\r
+\r
+        property int userIndex: 0\r
+\r
+        function openUserStatusDrawer(index) {\r
+            console.log(`About to show dialog for user with index ${index}`);\r
+            userIndex = index;\r
+            open();\r
+        }\r
+\r
+        Loader {\r
+            id: userStatusContents\r
+            anchors.fill: parent\r
+            active: userStatusDrawer.visible\r
+            sourceComponent: UserStatusSelectorPage {\r
+                anchors.fill: parent\r
+                userIndex: userStatusDrawer.userIndex\r
+                onFinished: userStatusDrawer.close()\r
+            }\r
+        }\r
+    }\r
+\r
+    Item {\r
+        id: trayWindowMainItem\r
 \r
         property bool isUnifiedSearchActive: unifiedSearchResultsListViewSkeletonLoader.active\r
                                              || unifiedSearchResultNothingFound.visible\r
@@ -117,10 +160,7 @@ Window {
                                              || unifiedSearchResultsListView.visible\r
 \r
         anchors.fill:   parent\r
-        radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius\r
-        border.width:   Style.trayWindowBorderWidth\r
-        border.color:   Style.menuBorder\r
-        color: Style.backgroundColor\r
+        clip: true\r
 \r
         Accessible.role: Accessible.Grouping\r
         Accessible.name: qsTr("Nextcloud desktop main dialog")\r
@@ -128,9 +168,9 @@ Window {
         Rectangle {\r
             id: trayWindowHeaderBackground\r
 \r
-            anchors.left:   trayWindowBackground.left\r
-            anchors.right:  trayWindowBackground.right\r
-            anchors.top:    trayWindowBackground.top\r
+            anchors.left:   trayWindowMainItem.left\r
+            anchors.right:  trayWindowMainItem.right\r
+            anchors.top:    trayWindowMainItem.top\r
             height:         Style.trayWindowHeaderHeight\r
             color:          UserModel.currentUser.headerColor\r
 \r
@@ -206,35 +246,12 @@ Window {
                             userLineInstantiator.active = true;\r
                         }\r
 \r
-                        Loader {\r
-                            id: userStatusSelectorDialogLoader\r
-\r
-                            property int userIndex\r
-\r
-                            function openDialog(newUserIndex) {\r
-                                console.log(`About to show dialog for user with index ${newUserIndex}`);\r
-                                userIndex = newUserIndex;\r
-                                active = true;\r
-                                item.show();\r
-                            }\r
-\r
-                            active: false\r
-                            sourceComponent: UserStatusSelectorDialog {\r
-                                userIndex: userStatusSelectorDialogLoader.userIndex\r
-                            }\r
-\r
-                            onLoaded: {\r
-                                item.model.load(userIndex);\r
-                                item.show();\r
-                            }\r
-                        }\r
-\r
                         Instantiator {\r
                             id: userLineInstantiator\r
                             model: UserModel\r
                             delegate: UserLine {\r
-                                onShowUserStatusSelectorDialog: {\r
-                                    userStatusSelectorDialogLoader.openDialog(model.index);\r
+                                onShowUserStatusSelector: {\r
+                                    userStatusDrawer.openUserStatusDrawer(model.index);\r
                                     accountMenu.close();\r
                                 }\r
                             }\r
@@ -661,8 +678,8 @@ Window {
 \r
             anchors {\r
                 top: trayWindowHeaderBackground.bottom\r
-                left: trayWindowBackground.left\r
-                right: trayWindowBackground.right\r
+                left: trayWindowMainItem.left\r
+                right: trayWindowMainItem.right\r
 \r
                 topMargin: Style.trayHorizontalMargin + controlRoot.padding\r
                 leftMargin: Style.trayHorizontalMargin + controlRoot.padding\r
@@ -681,8 +698,8 @@ Window {
             visible:  UserModel.currentUser.unifiedSearchResultsListModel.errorString && !unifiedSearchResultsListView.visible && ! UserModel.currentUser.unifiedSearchResultsListModel.isSearchInProgress && ! UserModel.currentUser.unifiedSearchResultsListModel.currentFetchMoreInProgressProviderId\r
             text:  UserModel.currentUser.unifiedSearchResultsListModel.errorString\r
             anchors.top: trayWindowUnifiedSearchInputContainer.bottom\r
-            anchors.left: trayWindowBackground.left\r
-            anchors.right: trayWindowBackground.right\r
+            anchors.left: trayWindowMainItem.left\r
+            anchors.right: trayWindowMainItem.right\r
             anchors.margins: Style.trayHorizontalMargin\r
         }\r
 \r
@@ -690,8 +707,8 @@ Window {
             id: unifiedSearchResultNothingFound\r
             visible: false\r
             anchors.top: trayWindowUnifiedSearchInputContainer.bottom\r
-            anchors.left: trayWindowBackground.left\r
-            anchors.right: trayWindowBackground.right\r
+            anchors.left: trayWindowMainItem.left\r
+            anchors.right: trayWindowMainItem.right\r
             anchors.topMargin: Style.trayHorizontalMargin\r
 \r
             text: UserModel.currentUser.unifiedSearchResultsListModel.searchTerm\r
@@ -724,9 +741,9 @@ Window {
         Loader {\r
             id: unifiedSearchResultsListViewSkeletonLoader\r
             anchors.top: trayWindowUnifiedSearchInputContainer.bottom\r
-            anchors.left: trayWindowBackground.left\r
-            anchors.right: trayWindowBackground.right\r
-            anchors.bottom: trayWindowBackground.bottom\r
+            anchors.left: trayWindowMainItem.left\r
+            anchors.right: trayWindowMainItem.right\r
+            anchors.bottom: trayWindowMainItem.bottom\r
 \r
             active: !unifiedSearchResultNothingFound.visible &&\r
                     !unifiedSearchResultsListView.visible &&\r
@@ -752,9 +769,9 @@ Window {
             visible: unifiedSearchResultsListView.count > 0\r
 \r
             anchors.top: trayWindowUnifiedSearchInputContainer.bottom\r
-            anchors.left: trayWindowBackground.left\r
-            anchors.right: trayWindowBackground.right\r
-            anchors.bottom: trayWindowBackground.bottom\r
+            anchors.left: trayWindowMainItem.left\r
+            anchors.right: trayWindowMainItem.right\r
+            anchors.bottom: trayWindowMainItem.bottom\r
 \r
             ListView {\r
                 id: unifiedSearchResultsListView\r
@@ -791,19 +808,19 @@ Window {
         SyncStatus {\r
             id: syncStatus\r
 \r
-            visible: !trayWindowBackground.isUnifiedSearchActive\r
+            visible: !trayWindowMainItem.isUnifiedSearchActive\r
 \r
             anchors.top: trayWindowUnifiedSearchInputContainer.bottom\r
-            anchors.left: trayWindowBackground.left\r
-            anchors.right: trayWindowBackground.right\r
+            anchors.left: trayWindowMainItem.left\r
+            anchors.right: trayWindowMainItem.right\r
         }\r
 \r
         ActivityList {\r
-            visible: !trayWindowBackground.isUnifiedSearchActive\r
+            visible: !trayWindowMainItem.isUnifiedSearchActive\r
             anchors.top: syncStatus.bottom\r
-            anchors.left: trayWindowBackground.left\r
-            anchors.right: trayWindowBackground.right\r
-            anchors.bottom: trayWindowBackground.bottom\r
+            anchors.left: trayWindowMainItem.left\r
+            anchors.right: trayWindowMainItem.right\r
+            anchors.bottom: trayWindowMainItem.bottom\r
 \r
             activeFocusOnTab: true\r
             model: activityModel\r
@@ -833,5 +850,5 @@ Window {
 \r
             onLoaded: refresh()\r
         }\r
-    } // Rectangle trayWindowBackground\r
+    } // Item trayWindowMainItem\r
 }\r
index 857c71305ffd5d474b5c6ec3e5b3d986aeec2da2..227d7bd4249d7c55050447bd8dca2f72e5dd083b 100644 (file)
@@ -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();
 }
 
index 9d7e393649b5e7a545f18950194314b556c9ff0c..9137986416b9b3eb64de53c8481c4262b1613059 100644 (file)
@@ -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;
index 5ea487e435173ab4595d1319551fd5201cce0468..d529f1a16ee91d3d6ee1d12000e4b38dc1950a36 100644 (file)
@@ -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()
index 06970bae098de798bdf3372c31a3a692ddab0df3..6d1adb5ec74c547d3362880319b66d8f55615463 100644 (file)
@@ -35,6 +35,8 @@ QtObject {
     property int trayWindowBorderWidth: variableSize(1)\r
     property int trayWindowHeaderHeight: variableSize(60)\r
     property int trayHorizontalMargin: 10\r
+    property int trayModalWidth: 380\r
+    property int trayModalHeight: 490\r
     property int trayListItemIconSize: accountAvatarSize\r
     property real thumbnailImageSizeReduction: 0.2  // We reserve some space within the thumbnail "item", here about 20%.\r
                                                     // This is because we need to also add the added/modified icon and we\r
@@ -43,6 +45,7 @@ QtObject {
                                                     // images, which will work so long as the thumbnails are left aligned\r
 \r
     property int standardSpacing: 10\r
+    property int smallSpacing: 5\r
 \r
     property int minActivityHeight: variableSize(40)\r
 \r