mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-21 20:45:51 +03:00
Group folder visibility improvements. Show dropdown in tray window.
Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
parent
b0a2150327
commit
3be820d9a3
14 changed files with 612 additions and 80 deletions
|
@ -52,5 +52,8 @@
|
||||||
<file>theme/Style/Style.qml</file>
|
<file>theme/Style/Style.qml</file>
|
||||||
<file>theme/Style/qmldir</file>
|
<file>theme/Style/qmldir</file>
|
||||||
<file>src/gui/filedetails/NCRadioButton.qml</file>
|
<file>src/gui/filedetails/NCRadioButton.qml</file>
|
||||||
|
<file>src/gui/tray/ListItemLineAndSubline.qml</file>
|
||||||
|
<file>src/gui/tray/TrayFoldersMenuButton.qml</file>
|
||||||
|
<file>src/gui/tray/TrayFolderListItem.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
52
src/gui/tray/ListItemLineAndSubline.qml
Normal file
52
src/gui/tray/ListItemLineAndSubline.qml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 by Oleksandr Zolotov <alex@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 QtQml 2.15
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
spacing: Style.standardSpacing
|
||||||
|
|
||||||
|
property string lineText: ""
|
||||||
|
property string sublineText: ""
|
||||||
|
|
||||||
|
property int titleFontSize: Style.unifiedSearchResultTitleFontSize
|
||||||
|
property int sublineFontSize: Style.unifiedSearchResultSublineFontSize
|
||||||
|
|
||||||
|
property color titleColor: Style.ncTextColor
|
||||||
|
property color sublineColor: Style.ncSecondaryTextColor
|
||||||
|
|
||||||
|
EnforcedPlainTextLabel {
|
||||||
|
id: title
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.lineText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: root.titleFontSize
|
||||||
|
color: root.titleColor
|
||||||
|
}
|
||||||
|
EnforcedPlainTextLabel {
|
||||||
|
id: subline
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.sublineText
|
||||||
|
visible: text !== ""
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: root.sublineFontSize
|
||||||
|
color: root.sublineColor
|
||||||
|
}
|
||||||
|
}
|
70
src/gui/tray/TrayFolderListItem.qml
Normal file
70
src/gui/tray/TrayFolderListItem.qml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 by Oleksandr Zolotov <alex@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 QtQml 2.15
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string subline: ""
|
||||||
|
property string iconSource: "image://svgimage-custom-color/folder-group.svg/" + Style.ncTextColor
|
||||||
|
property string toolTipText: root.text
|
||||||
|
|
||||||
|
NCToolTip {
|
||||||
|
visible: root.hovered && root.toolTipText !== ""
|
||||||
|
text: root.toolTipText
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
height: parent.height
|
||||||
|
width: parent.width
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.normalBorderWidth
|
||||||
|
color: parent.parent.hovered || parent.parent.visualFocus ? Style.lightHover : "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Style.trayWindowMenuEntriesMargin
|
||||||
|
anchors.rightMargin: Style.trayWindowMenuEntriesMargin
|
||||||
|
spacing: Style.trayHorizontalMargin
|
||||||
|
|
||||||
|
Image {
|
||||||
|
source: root.iconSource
|
||||||
|
cache: true
|
||||||
|
sourceSize.width: root.height * Style.smallIconScaleFactor
|
||||||
|
sourceSize.height: root.height * Style.smallIconScaleFactor
|
||||||
|
verticalAlignment: Qt.AlignVCenter
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
Layout.preferredHeight: root.height * Style.smallIconScaleFactor
|
||||||
|
Layout.preferredWidth: root.height * Style.smallIconScaleFactor
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
ListItemLineAndSubline {
|
||||||
|
lineText: root.text
|
||||||
|
sublineText: root.subline
|
||||||
|
|
||||||
|
spacing: Style.extraSmallSpacing
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
216
src/gui/tray/TrayFoldersMenuButton.qml
Normal file
216
src/gui/tray/TrayFoldersMenuButton.qml
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 by Oleksandr Zolotov <alex@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 QtQuick.Layouts 1.15
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
HeaderButton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
signal folderEntryTriggered(string fullFolderPath, bool isGroupFolder)
|
||||||
|
|
||||||
|
required property var currentUser
|
||||||
|
property bool userHasGroupFolders: currentUser.groupFolders.length > 0
|
||||||
|
|
||||||
|
function openMenu() {
|
||||||
|
foldersMenuLoader.openMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
foldersMenuLoader.closeMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMenuOpen() {
|
||||||
|
if (foldersMenuLoader.isMenuVisible) {
|
||||||
|
closeMenu()
|
||||||
|
} else {
|
||||||
|
openMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: currentUser.hasLocalFolder
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
flat: true
|
||||||
|
palette: Style.systemPalette
|
||||||
|
|
||||||
|
Accessible.role: root.userHasGroupFolders ? Accessible.ButtonMenu : Accessible.Button
|
||||||
|
Accessible.name: tooltip.text
|
||||||
|
Accessible.onPressAction: root.clicked()
|
||||||
|
|
||||||
|
NCToolTip {
|
||||||
|
id: tooltip
|
||||||
|
visible: root.hovered && !foldersMenuLoader.isMenuVisible
|
||||||
|
text: root.userHasGroupFolders ? qsTr("Open local or group folders") : qsTr("Open local folder")
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: folderStateIndicator
|
||||||
|
visible: root.currentUser.hasLocalFolder
|
||||||
|
source: root.currentUser.isConnected ? Style.stateOnlineImageSource : Style.stateOfflineImageSource
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
anchors.top: root.verticalCenter
|
||||||
|
anchors.left: root.horizontalCenter
|
||||||
|
sourceSize.width: Style.folderStateIndicatorSize
|
||||||
|
sourceSize.height: Style.folderStateIndicatorSize
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Indicator
|
||||||
|
Accessible.name: root.currentUser.isConnected ? qsTr("Connected") : qsTr("Disconnected")
|
||||||
|
z: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: folderStateIndicatorBackground
|
||||||
|
width: Style.folderStateIndicatorSize + Style.trayFolderStatusIndicatorSizeOffset
|
||||||
|
height: width
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: Style.currentUserHeaderColor
|
||||||
|
radius: width * Style.trayFolderStatusIndicatorRadiusFactor
|
||||||
|
z: -2
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: folderStateIndicatorBackgroundMouseHover
|
||||||
|
width: Style.folderStateIndicatorSize + Style.trayFolderStatusIndicatorSizeOffset
|
||||||
|
height: width
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: root.hovered ? Style.currentUserHeaderTextColor : "transparent"
|
||||||
|
opacity: Style.trayFolderStatusIndicatorMouseHoverOpacityFactor
|
||||||
|
radius: width * Style.trayFolderStatusIndicatorRadiusFactor
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: openLocalFolderButtonRowLayout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: openLocalFolderButtonIcon
|
||||||
|
cache: false
|
||||||
|
source: "qrc:///client/theme/white/folder.svg"
|
||||||
|
|
||||||
|
verticalAlignment: Qt.AlignCenter
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Graphic
|
||||||
|
Accessible.name: qsTr("Group folder button")
|
||||||
|
Layout.leftMargin: Style.trayHorizontalMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: openLocalFolderButtonCaretIconLoader
|
||||||
|
|
||||||
|
active: root.userHasGroupFolders
|
||||||
|
visible: active
|
||||||
|
|
||||||
|
sourceComponent: ColorOverlay {
|
||||||
|
width: source.width
|
||||||
|
height: source.height
|
||||||
|
cached: true
|
||||||
|
color: Style.currentUserHeaderTextColor
|
||||||
|
source: Image {
|
||||||
|
source: "qrc:///client/theme/white/caret-down.svg"
|
||||||
|
sourceSize.width: Style.accountDropDownCaretSize
|
||||||
|
sourceSize.height: Style.accountDropDownCaretSize
|
||||||
|
|
||||||
|
verticalAlignment: Qt.AlignCenter
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.margins: Style.accountDropDownCaretMargin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: foldersMenuLoader
|
||||||
|
|
||||||
|
property var openMenu: function(){}
|
||||||
|
property var closeMenu: function(){}
|
||||||
|
property bool isMenuVisible: false
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.userHasGroupFolders
|
||||||
|
visible: active
|
||||||
|
|
||||||
|
sourceComponent: AutoSizingMenu {
|
||||||
|
id: foldersMenu
|
||||||
|
|
||||||
|
x: Style.trayWindowMenuOffsetX
|
||||||
|
y: (root.y + root.height + Style.trayWindowMenuOffsetY)
|
||||||
|
width: Style.trayWindowWidth * Style.trayWindowMenuWidthFactor
|
||||||
|
height: implicitHeight + y > Style.trayWindowHeight ? Style.trayWindowHeight - y : implicitHeight
|
||||||
|
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
||||||
|
|
||||||
|
contentItem: ScrollView {
|
||||||
|
id: foldersMenuScrollView
|
||||||
|
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
data: WheelHandler {
|
||||||
|
target: foldersMenuScrollView.contentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: foldersMenuListView
|
||||||
|
|
||||||
|
implicitHeight: contentHeight
|
||||||
|
model: root.currentUser.groupFolders
|
||||||
|
interactive: true
|
||||||
|
clip: true
|
||||||
|
currentIndex: foldersMenu.currentIndex
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
delegate: TrayFolderListItem {
|
||||||
|
id: groupFoldersEntry
|
||||||
|
|
||||||
|
property bool isGroupFolder: model.modelData.isGroupFolder
|
||||||
|
|
||||||
|
text: model.modelData.name
|
||||||
|
toolTipText: !isGroupFolder ? qsTr("Open local folder \"%1\"").arg(model.modelData.fullPath) : qsTr("Open groupfolder \"%1\"").arg(model.modelData.fullPath)
|
||||||
|
subline: model.modelData.parentPath
|
||||||
|
width: foldersMenuListView.width
|
||||||
|
height: Style.standardPrimaryButtonHeight
|
||||||
|
iconSource: !isGroupFolder ? "image://svgimage-custom-color/folder.svg/" + Style.ncTextColor : "image://svgimage-custom-color/folder-group.svg/" + Style.ncTextColor
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
foldersMenu.close();
|
||||||
|
root.folderEntryTriggered(model.modelData.fullPath, isGroupFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
Accessible.role: Accessible.MenuItem
|
||||||
|
Accessible.name: qsTr("Open %1 in file explorer").arg(title)
|
||||||
|
Accessible.onPressAction: groupFoldersEntry.triggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Accessible.role: Accessible.PopupMenu
|
||||||
|
Accessible.name: qsTr("User group and local folders menu")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
foldersMenuLoader.openMenu = open
|
||||||
|
foldersMenuLoader.closeMenu = close
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
onVisibleChanged: foldersMenuLoader.isMenuVisible = visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ RowLayout {
|
||||||
property color titleColor: Style.ncTextColor
|
property color titleColor: Style.ncTextColor
|
||||||
property color sublineColor: Style.ncSecondaryTextColor
|
property color sublineColor: Style.ncSecondaryTextColor
|
||||||
|
|
||||||
|
|
||||||
Accessible.role: Accessible.ListItem
|
Accessible.role: Accessible.ListItem
|
||||||
Accessible.name: resultTitle
|
Accessible.name: resultTitle
|
||||||
Accessible.onPressAction: unifiedSearchResultMouseArea.clicked()
|
Accessible.onPressAction: unifiedSearchResultMouseArea.clicked()
|
||||||
|
@ -79,29 +80,16 @@ RowLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ListItemLineAndSubline {
|
||||||
id: unifiedSearchResultTextContainer
|
id: unifiedSearchResultTextContainer
|
||||||
|
|
||||||
|
spacing: Style.standardSpacing
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.rightMargin: Style.trayHorizontalMargin
|
Layout.rightMargin: Style.trayHorizontalMargin
|
||||||
spacing: Style.standardSpacing
|
|
||||||
|
|
||||||
EnforcedPlainTextLabel {
|
lineText: unifiedSearchResultItemDetails.title.replace(/[\r\n]+/g, " ")
|
||||||
id: unifiedSearchResultTitleText
|
sublineText: unifiedSearchResultItemDetails.subline.replace(/[\r\n]+/g, " ")
|
||||||
Layout.fillWidth: true
|
|
||||||
text: unifiedSearchResultItemDetails.title.replace(/[\r\n]+/g, " ")
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: unifiedSearchResultItemDetails.titleFontSize
|
|
||||||
color: unifiedSearchResultItemDetails.titleColor
|
|
||||||
}
|
|
||||||
EnforcedPlainTextLabel {
|
|
||||||
id: unifiedSearchResultTextSubline
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: unifiedSearchResultItemDetails.subline.replace(/[\r\n]+/g, " ")
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: unifiedSearchResultItemDetails.sublineFontSize
|
|
||||||
color: unifiedSearchResultItemDetails.sublineColor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ ApplicationWindow {
|
||||||
if(Systray.isOpen) {
|
if(Systray.isOpen) {
|
||||||
accountMenu.close();
|
accountMenu.close();
|
||||||
appsMenu.close();
|
appsMenu.close();
|
||||||
|
openLocalFolderButton.closeMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,25 +467,25 @@ ApplicationWindow {
|
||||||
id: currentAccountStatusIndicatorBackground
|
id: currentAccountStatusIndicatorBackground
|
||||||
visible: UserModel.currentUser.isConnected
|
visible: UserModel.currentUser.isConnected
|
||||||
&& UserModel.currentUser.serverHasUserStatus
|
&& UserModel.currentUser.serverHasUserStatus
|
||||||
width: Style.accountAvatarStateIndicatorSize + 2
|
width: Style.accountAvatarStateIndicatorSize + + Style.trayFolderStatusIndicatorSizeOffset
|
||||||
height: width
|
height: width
|
||||||
anchors.bottom: currentAccountAvatar.bottom
|
anchors.bottom: currentAccountAvatar.bottom
|
||||||
anchors.right: currentAccountAvatar.right
|
anchors.right: currentAccountAvatar.right
|
||||||
color: Style.currentUserHeaderColor
|
color: Style.currentUserHeaderColor
|
||||||
radius: width*0.5
|
radius: width * Style.trayFolderStatusIndicatorRadiusFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: currentAccountStatusIndicatorMouseHover
|
id: currentAccountStatusIndicatorMouseHover
|
||||||
visible: UserModel.currentUser.isConnected
|
visible: UserModel.currentUser.isConnected
|
||||||
&& UserModel.currentUser.serverHasUserStatus
|
&& UserModel.currentUser.serverHasUserStatus
|
||||||
width: Style.accountAvatarStateIndicatorSize + 2
|
width: Style.accountAvatarStateIndicatorSize + + Style.trayFolderStatusIndicatorSizeOffset
|
||||||
height: width
|
height: width
|
||||||
anchors.bottom: currentAccountAvatar.bottom
|
anchors.bottom: currentAccountAvatar.bottom
|
||||||
anchors.right: currentAccountAvatar.right
|
anchors.right: currentAccountAvatar.right
|
||||||
color: currentAccountButton.hovered ? Style.currentUserHeaderTextColor : "transparent"
|
color: currentAccountButton.hovered ? Style.currentUserHeaderTextColor : "transparent"
|
||||||
opacity: 0.2
|
opacity: Style.trayFolderStatusIndicatorMouseHoverOpacityFactor
|
||||||
radius: width*0.5
|
radius: width * Style.trayFolderStatusIndicatorRadiusFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
|
@ -586,62 +587,18 @@ ApplicationWindow {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
TrayFoldersMenuButton {
|
||||||
id: openLocalFolderRowLayout
|
id: openLocalFolderButton
|
||||||
spacing: 0
|
|
||||||
Layout.preferredWidth: Style.trayWindowHeaderHeight
|
|
||||||
Layout.preferredHeight: Style.trayWindowHeaderHeight
|
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
visible: currentUser.hasLocalFolder
|
||||||
Accessible.name: qsTr("Open local folder of current account")
|
currentUser: UserModel.currentUser
|
||||||
|
|
||||||
HeaderButton {
|
Layout.preferredWidth: Style.iconButtonWidth * Style.trayFolderListButtonWidthScaleFactor
|
||||||
id: openLocalFolderButton
|
Layout.alignment: Qt.AlignHCenter
|
||||||
visible: UserModel.currentUser.hasLocalFolder
|
|
||||||
icon.source: "qrc:///client/theme/white/folder.svg"
|
|
||||||
icon.color: Style.currentUserHeaderTextColor
|
|
||||||
onClicked: UserModel.openCurrentAccountLocalFolder()
|
|
||||||
|
|
||||||
Image {
|
onClicked: openLocalFolderButton.userHasGroupFolders ? openLocalFolderButton.toggleMenuOpen() : UserModel.openCurrentAccountLocalFolder()
|
||||||
id: folderStateIndicator
|
|
||||||
visible: UserModel.currentUser.hasLocalFolder
|
|
||||||
source: UserModel.currentUser.isConnected
|
|
||||||
? Style.stateOnlineImageSource
|
|
||||||
: Style.stateOfflineImageSource
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
anchors.top: openLocalFolderButton.verticalCenter
|
onFolderEntryTriggered: isGroupFolder ? UserModel.openCurrentAccountFolderFromTrayInfo(fullFolderPath) : UserModel.openCurrentAccountLocalFolder()
|
||||||
anchors.left: openLocalFolderButton.horizontalCenter
|
|
||||||
sourceSize.width: Style.folderStateIndicatorSize
|
|
||||||
sourceSize.height: Style.folderStateIndicatorSize
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Indicator
|
|
||||||
Accessible.name: UserModel.currentUser.isConnected ? qsTr("Connected") : qsTr("Disconnected")
|
|
||||||
z: 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: folderStateIndicatorBackground
|
|
||||||
width: Style.folderStateIndicatorSize + 2
|
|
||||||
height: width
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Style.currentUserHeaderColor
|
|
||||||
radius: width*0.5
|
|
||||||
z: -2
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: folderStateIndicatorBackgroundMouseHover
|
|
||||||
width: Style.folderStateIndicatorSize + 2
|
|
||||||
height: width
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: openLocalFolderButton.hovered ? Style.currentUserHeaderTextColor : "transparent"
|
|
||||||
opacity: 0.2
|
|
||||||
radius: width*0.5
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HeaderButton {
|
HeaderButton {
|
||||||
|
@ -678,9 +635,9 @@ ApplicationWindow {
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
id: appsMenu
|
id: appsMenu
|
||||||
x: -2
|
x: Style.trayWindowMenuOffsetX
|
||||||
y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2)
|
y: (trayWindowAppsButton.y + trayWindowAppsButton.height + Style.trayWindowMenuOffsetY)
|
||||||
width: Style.trayWindowWidth * 0.35
|
width: Style.trayWindowWidth * Style.trayWindowMenuWidthFactor
|
||||||
height: implicitHeight + y > Style.trayWindowHeight ? Style.trayWindowHeight - y : implicitHeight
|
height: implicitHeight + y > Style.trayWindowHeight ? Style.trayWindowHeight - y : implicitHeight
|
||||||
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,19 @@ constexpr qint64 activityDefaultExpirationTimeMsecs = 1000 * 60 * 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
|
TrayFolderInfo::TrayFolderInfo(const QString &name, const QString &parentPath, const QString &fullPath, FolderType folderType)
|
||||||
|
: _name(name)
|
||||||
|
, _parentPath(parentPath)
|
||||||
|
, _fullPath(fullPath)
|
||||||
|
, _folderType(folderType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrayFolderInfo::isGroupFolder() const
|
||||||
|
{
|
||||||
|
return _folderType == GroupFolder;
|
||||||
|
}
|
||||||
|
|
||||||
User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
|
User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
@ -76,6 +89,8 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
|
||||||
connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::headerTextColorChanged);
|
connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::headerTextColorChanged);
|
||||||
connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::accentColorChanged);
|
connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::accentColorChanged);
|
||||||
|
|
||||||
|
connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::slotAccountCapabilitiesChangedRefreshGroupFolders);
|
||||||
|
|
||||||
connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);
|
connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);
|
||||||
|
|
||||||
connect(this, &User::sendReplyMessage, this, &User::slotSendReplyMessage);
|
connect(this, &User::sendReplyMessage, this, &User::slotSendReplyMessage);
|
||||||
|
@ -299,6 +314,36 @@ void User::slotCheckExpiredActivities()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void User::parseNewGroupFolderPath(const QString &mountPoint)
|
||||||
|
{
|
||||||
|
if (mountPoint.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto mountPointSplit = mountPoint.split(QLatin1Char('/'), Qt::SkipEmptyParts);
|
||||||
|
|
||||||
|
if (mountPointSplit.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto groupFolderName = mountPointSplit.takeLast();
|
||||||
|
const auto parentPath = mountPointSplit.join(QLatin1Char('/'));
|
||||||
|
_trayFolderInfos.push_back(QVariant::fromValue(TrayFolderInfo{groupFolderName, parentPath, mountPoint, TrayFolderInfo::GroupFolder}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void User::prePendGroupFoldersWithLocalFolder()
|
||||||
|
{
|
||||||
|
if (!_trayFolderInfos.isEmpty() && !_trayFolderInfos.first().value<TrayFolderInfo>().isGroupFolder()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto localFolderName = getFolder()->shortGuiLocalPath();
|
||||||
|
auto localFolderPathSplit = getFolder()->path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
|
||||||
|
if (!localFolderPathSplit.isEmpty()) {
|
||||||
|
localFolderPathSplit.removeLast();
|
||||||
|
}
|
||||||
|
const auto localFolderParentPath = !localFolderPathSplit.isEmpty() ? localFolderPathSplit.join(QLatin1Char('/')) : "/";
|
||||||
|
_trayFolderInfos.push_front(QVariant::fromValue(TrayFolderInfo{localFolderName, localFolderParentPath, getFolder()->path(), TrayFolderInfo::Folder}));
|
||||||
|
}
|
||||||
|
|
||||||
void User::connectPushNotifications() const
|
void User::connectPushNotifications() const
|
||||||
{
|
{
|
||||||
connect(_account->account().data(), &Account::pushNotificationsDisabled, this, &User::slotDisconnectPushNotifications, Qt::UniqueConnection);
|
connect(_account->account().data(), &Account::pushNotificationsDisabled, this, &User::slotDisconnectPushNotifications, Qt::UniqueConnection);
|
||||||
|
@ -747,6 +792,11 @@ void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QVariantList &User::groupFolders() const
|
||||||
|
{
|
||||||
|
return _trayFolderInfos;
|
||||||
|
}
|
||||||
|
|
||||||
void User::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
void User::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
||||||
{
|
{
|
||||||
auto folderInstance = FolderMan::instance()->folder(folder);
|
auto folderInstance = FolderMan::instance()->folder(folder);
|
||||||
|
@ -804,6 +854,42 @@ void User::openLocalFolder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void User::openFolderLocallyOrInBrowser(const QString &fullRemotePath)
|
||||||
|
{
|
||||||
|
const auto folder = getFolder();
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove remote path prefix and leading slash
|
||||||
|
auto fullRemotePathToPathInDb = folder->remotePath() != QStringLiteral("/") ? fullRemotePath.mid(folder->remotePathTrailingSlash().size()) : fullRemotePath;
|
||||||
|
if (fullRemotePathToPathInDb.startsWith("/")) {
|
||||||
|
fullRemotePathToPathInDb = fullRemotePathToPathInDb.mid(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncJournalFileRecord rec;
|
||||||
|
if (folder->journalDb()->getFileRecord(fullRemotePathToPathInDb, &rec) && rec.isValid()) {
|
||||||
|
// found folder locally, going to open
|
||||||
|
qCInfo(lcActivity) << "Opening locally a folder" << fullRemotePath;
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(folder->path() + rec.path()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to open it in browser
|
||||||
|
auto folderUrlForBrowser = Utility::concatUrlPath(_account->account()->url(), QStringLiteral("/index.php/apps/files/"));
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
urlQuery.addQueryItem(QStringLiteral("dir"), fullRemotePath);
|
||||||
|
folderUrlForBrowser.setQuery(urlQuery);
|
||||||
|
if (!folderUrlForBrowser.scheme().startsWith(QStringLiteral("http"))) {
|
||||||
|
folderUrlForBrowser.setScheme(QStringLiteral("https"));
|
||||||
|
}
|
||||||
|
// open https://server.com/index.php/apps/files/?dir=/group_folder/path
|
||||||
|
qCInfo(lcActivity) << "Opening in browser a folder" << fullRemotePath;
|
||||||
|
Utility::openBrowser(folderUrlForBrowser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void User::login() const
|
void User::login() const
|
||||||
{
|
{
|
||||||
_account->account()->resetRejectedCertificates();
|
_account->account()->resetRejectedCertificates();
|
||||||
|
@ -945,6 +1031,99 @@ void User::forceSyncNow() const
|
||||||
FolderMan::instance()->forceSyncForFolder(getFolder());
|
FolderMan::instance()->forceSyncForFolder(getFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void User::slotAccountCapabilitiesChangedRefreshGroupFolders()
|
||||||
|
{
|
||||||
|
if (!_account->account()->capabilities().groupFoldersAvailable()) {
|
||||||
|
if (!_trayFolderInfos.isEmpty()) {
|
||||||
|
_trayFolderInfos.clear();
|
||||||
|
emit groupFoldersChanged();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slotFetchGroupFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
void User::slotFetchGroupFolders()
|
||||||
|
{
|
||||||
|
QNetworkRequest req;
|
||||||
|
req.setRawHeader(QByteArrayLiteral("OCS-APIREQUEST"), QByteArrayLiteral("true"));
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
|
||||||
|
query.addQueryItem(QLatin1String("applicable"), QLatin1String("1"));
|
||||||
|
QUrl groupFolderListUrl = Utility::concatUrlPath(_account->account()->url(), QStringLiteral("/index.php/apps/groupfolders/folders"));
|
||||||
|
groupFolderListUrl.setQuery(query);
|
||||||
|
|
||||||
|
const auto groupFolderListJob = _account->account()->sendRequest(QByteArrayLiteral("GET"), groupFolderListUrl, req);
|
||||||
|
connect(groupFolderListJob, &SimpleNetworkJob::finishedSignal, this, &User::slotGroupFoldersFetched);
|
||||||
|
}
|
||||||
|
|
||||||
|
void User::slotGroupFoldersFetched(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
Q_ASSERT(reply);
|
||||||
|
if (!reply) {
|
||||||
|
qCWarning(lcActivity) << "Group folders fetch error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto oldSize = _trayFolderInfos.size();
|
||||||
|
const auto oldTrayFolderInfos = _trayFolderInfos;
|
||||||
|
_trayFolderInfos.clear();
|
||||||
|
|
||||||
|
const auto replyData = reply->readAll();
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
if (oldSize != _trayFolderInfos.size()) {
|
||||||
|
emit groupFoldersChanged();
|
||||||
|
}
|
||||||
|
qCWarning(lcActivity) << "Group folders fetch error" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << replyData;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError jsonParseError{};
|
||||||
|
const auto json = QJsonDocument::fromJson(replyData, &jsonParseError);
|
||||||
|
|
||||||
|
if (jsonParseError.error != QJsonParseError::NoError) {
|
||||||
|
qCWarning(lcActivity) << "Group folders JSON parse error" << jsonParseError.error << jsonParseError.errorString();
|
||||||
|
if (oldSize != _trayFolderInfos.size()) {
|
||||||
|
emit groupFoldersChanged();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto obj = json.object().toVariantMap();
|
||||||
|
const auto groupFolders = obj["ocs"].toMap()["data"].toMap();
|
||||||
|
|
||||||
|
for (const auto &groupFolder : groupFolders.values()) {
|
||||||
|
const auto groupFolderInfo = groupFolder.toMap();
|
||||||
|
const auto mountPoint = groupFolderInfo.value(QStringLiteral("mount_point"), {}).toString();
|
||||||
|
parseNewGroupFolderPath(mountPoint);
|
||||||
|
}
|
||||||
|
std::sort(std::begin(_trayFolderInfos), std::end(_trayFolderInfos), [](const auto &leftVariant, const auto &rightVariant) {
|
||||||
|
const auto folderInfoA = leftVariant.template value<TrayFolderInfo>();
|
||||||
|
const auto folderInfoB = rightVariant.template value<TrayFolderInfo>();
|
||||||
|
return folderInfoA._fullPath < folderInfoB._fullPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!_trayFolderInfos.isEmpty()) {
|
||||||
|
if (hasLocalFolder()) {
|
||||||
|
prePendGroupFoldersWithLocalFolder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldSize != _trayFolderInfos.size()) {
|
||||||
|
emit groupFoldersChanged();
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < oldTrayFolderInfos.size(); ++i) {
|
||||||
|
const auto oldFolderInfo = oldTrayFolderInfos.at(i).template value<TrayFolderInfo>();
|
||||||
|
const auto newFolderInfo = _trayFolderInfos.at(i).template value<TrayFolderInfo>();
|
||||||
|
if (oldFolderInfo._folderType != newFolderInfo._folderType || oldFolderInfo._fullPath != newFolderInfo._fullPath) {
|
||||||
|
break;
|
||||||
|
emit groupFoldersChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
UserModel *UserModel::_instance = nullptr;
|
UserModel *UserModel::_instance = nullptr;
|
||||||
|
@ -1105,6 +1284,15 @@ void UserModel::openCurrentAccountServer()
|
||||||
QDesktopServices::openUrl(url);
|
QDesktopServices::openUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UserModel::openCurrentAccountFolderFromTrayInfo(const QString &fullRemotePath)
|
||||||
|
{
|
||||||
|
if (_currentUserId < 0 || _currentUserId >= _users.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_users[_currentUserId]->openFolderLocallyOrInBrowser(fullRemotePath);
|
||||||
|
}
|
||||||
|
|
||||||
void UserModel::setCurrentUserId(const int id)
|
void UserModel::setCurrentUserId(const int id)
|
||||||
{
|
{
|
||||||
Q_ASSERT(id < _users.size());
|
Q_ASSERT(id < _users.size());
|
||||||
|
@ -1296,7 +1484,6 @@ int UserModel::findUserIdForAccount(AccountState *account) const
|
||||||
const auto id = std::distance(std::cbegin(_users), it);
|
const auto id = std::distance(std::cbegin(_users), it);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
ImageProvider::ImageProvider()
|
ImageProvider::ImageProvider()
|
||||||
|
|
|
@ -19,6 +19,28 @@
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
class UnifiedSearchResultsListModel;
|
class UnifiedSearchResultsListModel;
|
||||||
|
|
||||||
|
|
||||||
|
class TrayFolderInfo
|
||||||
|
{
|
||||||
|
Q_GADGET
|
||||||
|
|
||||||
|
Q_PROPERTY(QString name MEMBER _name)
|
||||||
|
Q_PROPERTY(QString parentPath MEMBER _parentPath)
|
||||||
|
Q_PROPERTY(QString fullPath MEMBER _fullPath)
|
||||||
|
Q_PROPERTY(bool isGroupFolder READ isGroupFolder CONSTANT)
|
||||||
|
public:
|
||||||
|
enum FolderType { Folder, GroupFolder };
|
||||||
|
|
||||||
|
TrayFolderInfo(const QString &name, const QString &parentPath, const QString &fullPath, FolderType folderType);
|
||||||
|
TrayFolderInfo() = default;
|
||||||
|
[[nodiscard]] bool isGroupFolder() const;
|
||||||
|
|
||||||
|
QString _name;
|
||||||
|
QString _parentPath;
|
||||||
|
QString _fullPath;
|
||||||
|
FolderType _folderType = Folder;
|
||||||
|
};
|
||||||
|
|
||||||
class User : public QObject
|
class User : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -37,6 +59,7 @@ class User : public QObject
|
||||||
Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged)
|
Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged)
|
||||||
Q_PROPERTY(bool isConnected READ isConnected NOTIFY accountStateChanged)
|
Q_PROPERTY(bool isConnected READ isConnected NOTIFY accountStateChanged)
|
||||||
Q_PROPERTY(UnifiedSearchResultsListModel* unifiedSearchResultsListModel READ getUnifiedSearchResultsListModel CONSTANT)
|
Q_PROPERTY(UnifiedSearchResultsListModel* unifiedSearchResultsListModel READ getUnifiedSearchResultsListModel CONSTANT)
|
||||||
|
Q_PROPERTY(QVariantList groupFolders READ groupFolders NOTIFY groupFoldersChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
User(AccountStatePtr &account, const bool &isCurrent = false, QObject *parent = nullptr);
|
User(AccountStatePtr &account, const bool &isCurrent = false, QObject *parent = nullptr);
|
||||||
|
@ -51,6 +74,7 @@ public:
|
||||||
ActivityListModel *getActivityModel();
|
ActivityListModel *getActivityModel();
|
||||||
[[nodiscard]] UnifiedSearchResultsListModel *getUnifiedSearchResultsListModel() const;
|
[[nodiscard]] UnifiedSearchResultsListModel *getUnifiedSearchResultsListModel() const;
|
||||||
void openLocalFolder();
|
void openLocalFolder();
|
||||||
|
void openFolderLocallyOrInBrowser(const QString &fullRemotePath);
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] QString server(bool shortened = true) const;
|
[[nodiscard]] QString server(bool shortened = true) const;
|
||||||
[[nodiscard]] bool hasLocalFolder() const;
|
[[nodiscard]] bool hasLocalFolder() const;
|
||||||
|
@ -73,6 +97,7 @@ public:
|
||||||
[[nodiscard]] QUrl statusIcon() const;
|
[[nodiscard]] QUrl statusIcon() const;
|
||||||
[[nodiscard]] QString statusEmoji() const;
|
[[nodiscard]] QString statusEmoji() const;
|
||||||
void processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr &item);
|
void processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr &item);
|
||||||
|
[[nodiscard]] const QVariantList &groupFolders() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
|
@ -86,6 +111,7 @@ signals:
|
||||||
void headerTextColorChanged();
|
void headerTextColorChanged();
|
||||||
void accentColorChanged();
|
void accentColorChanged();
|
||||||
void sendReplyMessage(const int activityIndex, const QString &conversationToken, const QString &message, const QString &replyTo);
|
void sendReplyMessage(const int activityIndex, const QString &conversationToken, const QString &message, const QString &replyTo);
|
||||||
|
void groupFoldersChanged();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotItemCompleted(const QString &folder, const OCC::SyncFileItemPtr &item);
|
void slotItemCompleted(const QString &folder, const OCC::SyncFileItemPtr &item);
|
||||||
|
@ -109,6 +135,8 @@ public slots:
|
||||||
void slotRebuildNavigationAppList();
|
void slotRebuildNavigationAppList();
|
||||||
void slotSendReplyMessage(const int activityIndex, const QString &conversationToken, const QString &message, const QString &replyTo);
|
void slotSendReplyMessage(const int activityIndex, const QString &conversationToken, const QString &message, const QString &replyTo);
|
||||||
void forceSyncNow() const;
|
void forceSyncNow() const;
|
||||||
|
void slotAccountCapabilitiesChangedRefreshGroupFolders();
|
||||||
|
void slotFetchGroupFolders();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotPushNotificationsReady();
|
void slotPushNotificationsReady();
|
||||||
|
@ -116,7 +144,7 @@ private slots:
|
||||||
void slotReceivedPushNotification(OCC::Account *account);
|
void slotReceivedPushNotification(OCC::Account *account);
|
||||||
void slotReceivedPushActivity(OCC::Account *account);
|
void slotReceivedPushActivity(OCC::Account *account);
|
||||||
void slotCheckExpiredActivities();
|
void slotCheckExpiredActivities();
|
||||||
|
void slotGroupFoldersFetched(QNetworkReply *reply);
|
||||||
void checkNotifiedNotifications();
|
void checkNotifiedNotifications();
|
||||||
void showDesktopNotification(const QString &title, const QString &message, const long notificationId);
|
void showDesktopNotification(const QString &title, const QString &message, const long notificationId);
|
||||||
void showDesktopNotification(const OCC::Activity &activity);
|
void showDesktopNotification(const OCC::Activity &activity);
|
||||||
|
@ -124,6 +152,8 @@ private slots:
|
||||||
void showDesktopTalkNotification(const OCC::Activity &activity);
|
void showDesktopTalkNotification(const OCC::Activity &activity);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void prePendGroupFoldersWithLocalFolder();
|
||||||
|
void parseNewGroupFolderPath(const QString &path);
|
||||||
void connectPushNotifications() const;
|
void connectPushNotifications() const;
|
||||||
[[nodiscard]] bool checkPushNotificationsAreReady() const;
|
[[nodiscard]] bool checkPushNotificationsAreReady() const;
|
||||||
|
|
||||||
|
@ -138,6 +168,8 @@ private:
|
||||||
ActivityListModel *_activityModel;
|
ActivityListModel *_activityModel;
|
||||||
UnifiedSearchResultsListModel *_unifiedSearchResultsModel;
|
UnifiedSearchResultsListModel *_unifiedSearchResultsModel;
|
||||||
ActivityList _blacklistedNotifications;
|
ActivityList _blacklistedNotifications;
|
||||||
|
|
||||||
|
QVariantList _trayFolderInfos;
|
||||||
|
|
||||||
QTimer _expiredActivitiesCheckTimer;
|
QTimer _expiredActivitiesCheckTimer;
|
||||||
QTimer _notificationCheckTimer;
|
QTimer _notificationCheckTimer;
|
||||||
|
@ -158,6 +190,7 @@ class UserModel : public QAbstractListModel
|
||||||
Q_PROPERTY(User* currentUser READ currentUser NOTIFY currentUserChanged)
|
Q_PROPERTY(User* currentUser READ currentUser NOTIFY currentUserChanged)
|
||||||
Q_PROPERTY(int currentUserId READ currentUserId WRITE setCurrentUserId NOTIFY currentUserChanged)
|
Q_PROPERTY(int currentUserId READ currentUserId WRITE setCurrentUserId NOTIFY currentUserChanged)
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static UserModel *instance();
|
static UserModel *instance();
|
||||||
~UserModel() override = default;
|
~UserModel() override = default;
|
||||||
|
|
||||||
|
@ -208,6 +241,7 @@ public slots:
|
||||||
void openCurrentAccountLocalFolder();
|
void openCurrentAccountLocalFolder();
|
||||||
void openCurrentAccountTalk();
|
void openCurrentAccountTalk();
|
||||||
void openCurrentAccountServer();
|
void openCurrentAccountServer();
|
||||||
|
void openCurrentAccountFolderFromTrayInfo(const QString &fullRemotePath);
|
||||||
void setCurrentUserId(const int id);
|
void setCurrentUserId(const int id);
|
||||||
void login(const int id);
|
void login(const int id);
|
||||||
void logout(const int id);
|
void logout(const int id);
|
||||||
|
|
|
@ -350,6 +350,11 @@ bool Capabilities::uploadConflictFiles() const
|
||||||
return _capabilities[QStringLiteral("uploadConflictFiles")].toBool();
|
return _capabilities[QStringLiteral("uploadConflictFiles")].toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Capabilities::groupFoldersAvailable() const
|
||||||
|
{
|
||||||
|
return _capabilities[QStringLiteral("groupfolders")].toMap().value(QStringLiteral("hasGroupFolders"), false).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
QStringList Capabilities::blacklistedFiles() const
|
QStringList Capabilities::blacklistedFiles() const
|
||||||
{
|
{
|
||||||
return _capabilities["files"].toMap()["blacklisted_files"].toStringList();
|
return _capabilities["files"].toMap()["blacklisted_files"].toStringList();
|
||||||
|
|
|
@ -167,6 +167,8 @@ public:
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool uploadConflictFiles() const;
|
[[nodiscard]] bool uploadConflictFiles() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool groupFoldersAvailable() const;
|
||||||
|
|
||||||
// Direct Editing
|
// Direct Editing
|
||||||
void addDirectEditor(DirectEditor* directEditor);
|
void addDirectEditor(DirectEditor* directEditor);
|
||||||
DirectEditor* getDirectEditorForMimetype(const QMimeType &mimeType);
|
DirectEditor* getDirectEditorForMimetype(const QMimeType &mimeType);
|
||||||
|
|
|
@ -368,6 +368,7 @@ void DiscoverySingleDirectoryJob::start()
|
||||||
<< "http://owncloud.org/ns:dDC"
|
<< "http://owncloud.org/ns:dDC"
|
||||||
<< "http://owncloud.org/ns:permissions"
|
<< "http://owncloud.org/ns:permissions"
|
||||||
<< "http://owncloud.org/ns:checksums";
|
<< "http://owncloud.org/ns:checksums";
|
||||||
|
|
||||||
if (_isRootPath)
|
if (_isRootPath)
|
||||||
props << "http://owncloud.org/ns:data-fingerprint";
|
props << "http://owncloud.org/ns:data-fingerprint";
|
||||||
if (_account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) {
|
if (_account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) {
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
<file>theme/colored/state-warning-256.png</file>
|
<file>theme/colored/state-warning-256.png</file>
|
||||||
<file>theme/black/folder.png</file>
|
<file>theme/black/folder.png</file>
|
||||||
<file>theme/black/folder.svg</file>
|
<file>theme/black/folder.svg</file>
|
||||||
|
<file>theme/black/folder-group.svg</file>
|
||||||
<file>theme/black/folder@2x.png</file>
|
<file>theme/black/folder@2x.png</file>
|
||||||
<file>theme/white/folder.png</file>
|
<file>theme/white/folder.png</file>
|
||||||
<file>theme/white/folder@2x.png</file>
|
<file>theme/white/folder@2x.png</file>
|
||||||
|
|
|
@ -51,6 +51,7 @@ QtObject {
|
||||||
|
|
||||||
property int standardSpacing: 10
|
property int standardSpacing: 10
|
||||||
property int smallSpacing: 5
|
property int smallSpacing: 5
|
||||||
|
property int extraSmallSpacing: 2
|
||||||
|
|
||||||
property int iconButtonWidth: 36
|
property int iconButtonWidth: 36
|
||||||
property int standardPrimaryButtonHeight: 40
|
property int standardPrimaryButtonHeight: 40
|
||||||
|
@ -131,6 +132,20 @@ QtObject {
|
||||||
|
|
||||||
readonly property int activityContentSpace: 4
|
readonly property int activityContentSpace: 4
|
||||||
|
|
||||||
|
readonly property double smallIconScaleFactor: 0.6
|
||||||
|
|
||||||
|
readonly property double trayFolderListButtonWidthScaleFactor: 1.75
|
||||||
|
readonly property int trayFolderStatusIndicatorSizeOffset: 2
|
||||||
|
readonly property double trayFolderStatusIndicatorRadiusFactor: 0.5
|
||||||
|
readonly property double trayFolderStatusIndicatorMouseHoverOpacityFactor: 0.2
|
||||||
|
|
||||||
|
readonly property double trayWindowMenuWidthFactor: 0.35
|
||||||
|
|
||||||
|
readonly property int trayWindowMenuOffsetX: -2
|
||||||
|
readonly property int trayWindowMenuOffsetY: 2
|
||||||
|
|
||||||
|
readonly property int trayWindowMenuEntriesMargin: 6
|
||||||
|
|
||||||
function variableSize(size) {
|
function variableSize(size) {
|
||||||
return size * (1 + Math.min(pixelSize / 100, 1));
|
return size * (1 + Math.min(pixelSize / 100, 1));
|
||||||
}
|
}
|
||||||
|
|
1
theme/black/folder-group.svg
Normal file
1
theme/black/folder-group.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" width="32" version="1.1"><path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2.921875 4 C 2.421875 4 2 4.421875 2 4.921875 L 2 27.078125 C 2 27.59375 2.40625 28 2.921875 28 L 29.078125 28 C 29.59375 28 30 27.59375 30 27.078125 L 30 8.925781 C 29.996094 8.417969 29.585938 8.003906 29.078125 7.992188 L 16 7.992188 L 12 4 Z M 18.085938 11.640625 C 19.429688 11.640625 20.566406 12.621094 20.566406 13.886719 L 20.566406 13.914062 C 20.550781 14.308594 20.550781 14.761719 20.28125 15.851562 L 20.257812 15.875 C 20.183594 16.0625 20.078125 16.238281 19.945312 16.394531 C 19.8125 16.566406 19.65625 16.738281 19.507812 16.910156 L 19.328125 17.117188 C 19.300781 17.21875 19.269531 17.324219 19.25 17.425781 C 19.175781 17.800781 19.210938 18.042969 19.25 18.125 C 19.914062 18.40625 20.585938 18.660156 21.160156 18.902344 C 21.789062 19.167969 22.328125 19.429688 22.6875 19.859375 C 22.738281 19.917969 22.773438 19.988281 22.789062 20.066406 C 22.828125 20.375 22.859375 20.898438 22.894531 21.433594 C 22.929688 21.96875 22.964844 22.515625 22.996094 22.753906 C 23.015625 22.921875 22.933594 23.082031 22.789062 23.167969 C 21.65625 23.769531 19.875 24.035156 18.085938 24.042969 C 16.296875 24.050781 14.5 23.800781 13.332031 23.167969 C 13.1875 23.082031 13.105469 22.921875 13.125 22.753906 C 13.167969 22.410156 13.210938 21.984375 13.261719 21.558594 L 13.21875 21.5625 C 11.988281 21.566406 10.757812 21.394531 9.957031 20.960938 C 9.859375 20.902344 9.804688 20.789062 9.816406 20.675781 C 9.882812 20.148438 9.96875 19.363281 10.027344 18.832031 C 10.027344 18.820312 10.027344 18.808594 10.027344 18.796875 C 10.089844 18.539062 10.269531 18.449219 10.453125 18.335938 C 10.636719 18.222656 10.863281 18.125 11.109375 18.019531 C 11.550781 17.828125 12.042969 17.660156 12.4375 17.523438 C 12.457031 17.359375 12.445312 17.191406 12.402344 17.027344 L 12.332031 16.722656 L 12.296875 16.6875 C 12.179688 16.566406 12.066406 16.445312 11.960938 16.316406 C 11.859375 16.199219 11.785156 16.113281 11.730469 15.960938 L 11.710938 15.945312 L 11.710938 15.925781 C 11.597656 15.492188 11.53125 15.046875 11.515625 14.597656 C 11.515625 13.726562 12.296875 13.054688 13.21875 13.054688 C 14.136719 13.054688 14.921875 13.726562 14.921875 14.597656 L 14.921875 14.617188 C 14.910156 14.886719 14.910156 15.199219 14.726562 15.945312 L 14.707031 15.964844 C 14.65625 16.09375 14.585938 16.210938 14.496094 16.316406 C 14.40625 16.433594 14.296875 16.554688 14.195312 16.671875 L 14.070312 16.8125 C 14.050781 16.882812 14.03125 16.960938 14.015625 17.027344 C 13.972656 17.183594 13.972656 17.347656 14.015625 17.503906 C 14.472656 17.699219 14.9375 17.871094 15.328125 18.035156 C 15.628906 18.160156 15.882812 18.304688 16.101562 18.46875 C 16.390625 18.367188 16.695312 18.238281 16.949219 18.152344 C 16.976562 17.910156 16.957031 17.664062 16.898438 17.429688 C 16.867188 17.289062 16.824219 17.132812 16.792969 16.988281 L 16.742188 16.9375 C 16.570312 16.761719 16.40625 16.582031 16.25 16.394531 C 16.101562 16.222656 15.988281 16.097656 15.914062 15.878906 L 15.890625 15.851562 L 15.890625 15.824219 C 15.648438 14.78125 15.613281 14.289062 15.605469 13.886719 C 15.605469 12.621094 16.742188 11.640625 18.085938 11.640625 Z M 18.085938 11.640625 "/></svg>
|
After Width: | Height: | Size: 3.3 KiB |
Loading…
Reference in a new issue