diff --git a/.github/workflows/macos-build-and-test.yml b/.github/workflows/macos-build-and-test.yml index ffb3a1071e92b..90a6296d2068c 100644 --- a/.github/workflows/macos-build-and-test.yml +++ b/.github/workflows/macos-build-and-test.yml @@ -78,6 +78,10 @@ jobs: timeout-minutes: 60 runs-on: macos-26 steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 1 + - name: Restore cached Craft directories containing the built client uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: diff --git a/resources.qrc b/resources.qrc index a9e58cda6e896..cb4cc6218d7ff 100644 --- a/resources.qrc +++ b/resources.qrc @@ -40,6 +40,8 @@ src/gui/tray/TrayWindowHeader.qml src/gui/activity/qml/ActivityItemContextMenu.qml src/gui/activity/qml/ActivityItemActions.qml + src/gui/activity/qml/ActivityFileMenu.qml + src/gui/activity/qml/ActivityFileMenuButton.qml src/gui/activity/qml/ActivityItemContent.qml src/gui/activity/qml/TalkReplyTextField.qml src/gui/tray/CallNotificationDialog.qml diff --git a/src/gui/activity/qml/ActivityFileMenu.qml b/src/gui/activity/qml/ActivityFileMenu.qml new file mode 100644 index 0000000000000..465648d92e05b --- /dev/null +++ b/src/gui/activity/qml/ActivityFileMenu.qml @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls +import "../../tray" + +Menu { + id: root + + required property string filePath + required property bool serverHasIntegration + required property int itemFontPixelSize + + signal fileDetailsRequested(string filePath) + signal fileActionsRequested(string filePath) + + closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape + + property Action fileDetailsAction: Action { + id: fileDetailsAction + objectName: "fileDetailsAction" + text: qsTr("File details") + onTriggered: root.fileDetailsRequested(root.filePath) + } + + property Action fileActionsAction: Action { + id: fileActionsAction + objectName: "fileActionsAction" + text: qsTr("File actions") + onTriggered: root.fileActionsRequested(root.filePath) + } + + MenuItem { + id: fileDetailsMenuItem + action: root.fileDetailsAction + } + + MenuItem { + id: fileActionsMenuItem + action: root.fileActionsAction + visible: root.serverHasIntegration + } +} diff --git a/src/gui/activity/qml/ActivityFileMenuButton.qml b/src/gui/activity/qml/ActivityFileMenuButton.qml new file mode 100644 index 0000000000000..5e6d16afd1fef --- /dev/null +++ b/src/gui/activity/qml/ActivityFileMenuButton.qml @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls + +Button { + id: root + + required property string filePath + required property bool serverHasIntegration + required property int itemFontPixelSize + required property int buttonWidth + required property int buttonHeight + required property int buttonIconSize + + readonly property alias menu: fileMenu + + signal fileDetailsRequested(string filePath) + signal fileActionsRequested(string filePath) + + width: buttonWidth + height: buttonHeight + + icon.name: "view-more-symbolic" + icon.source: "image://svgimage-custom-color/more.svg/" + palette.buttonText + icon.width: buttonIconSize + icon.height: buttonIconSize + + ToolTip { + popupType: Qt.platform.os === "windows" ? Popup.Item : Popup.Native + text: qsTr("Open file details") + visible: parent.hovered + } + + display: Button.IconOnly + onClicked: fileMenu.visible ? fileMenu.close() : fileMenu.popup() + + ActivityFileMenu { + id: fileMenu + + filePath: root.filePath + serverHasIntegration: root.serverHasIntegration + itemFontPixelSize: root.itemFontPixelSize + + onFileDetailsRequested: path => root.fileDetailsRequested(path) + onFileActionsRequested: path => root.fileActionsRequested(path) + } +} diff --git a/src/gui/activity/qml/ActivityItemContent.qml b/src/gui/activity/qml/ActivityItemContent.qml index 822259d97d734..253c164e7c522 100644 --- a/src/gui/activity/qml/ActivityItemContent.qml +++ b/src/gui/activity/qml/ActivityItemContent.qml @@ -170,48 +170,19 @@ RowLayout { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter spacing: Style.extraSmallSpacing - Button { + ActivityFileMenuButton { id: fileDetailsButton - width: Style.activityListButtonWidth - height: Style.activityListButtonHeight - - icon.name: 'view-more-symbolic' - icon.source: "image://svgimage-custom-color/more.svg/" + palette.buttonText - icon.width: Style.activityListButtonIconSize - icon.height: Style.activityListButtonIconSize - - ToolTip { - popupType: Qt.platform.os === "windows" ? Popup.Item : Qt.platform.os === "windows" ? Popup.Item : Popup.Native - text: qsTr("Open file details") - visible: parent.hovered - } - - display: Button.IconOnly + buttonWidth: Style.activityListButtonWidth + buttonHeight: Style.activityListButtonHeight + buttonIconSize: Style.activityListButtonIconSize + itemFontPixelSize: Style.topLinePixelSize + filePath: root.activityData.openablePath + serverHasIntegration: root.activityData.serverHasIntegration visible: model.showFileDetails - onClicked: fileMoreButtonMenu.visible ? fileMoreButtonMenu.close() : fileMoreButtonMenu.popup() - - AutoSizingMenu { - id: fileMoreButtonMenu - closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape - - MenuItem { - height: visible ? implicitHeight : 0 - text: qsTr("File details") - font.pixelSize: Style.topLinePixelSize - hoverEnabled: true - onClicked: Systray.presentShareViewInTray(model.openablePath) - } - - MenuItem { - visible: model.serverHasIntegration - height: visible ? implicitHeight : 0 - text: qsTr("File actions") - font.pixelSize: Style.topLinePixelSize - hoverEnabled: true - onClicked: Systray.presentFileActionsViewInSystray(model.openablePath) - } - } + + onFileDetailsRequested: path => Systray.presentShareViewInTray(path) + onFileActionsRequested: path => Systray.presentFileActionsViewInSystray(path) } Button { diff --git a/src/gui/tray/AutoSizingMenu.qml b/src/gui/tray/AutoSizingMenu.qml index e3cc79871d459..835bcaa1bdd57 100644 --- a/src/gui/tray/AutoSizingMenu.qml +++ b/src/gui/tray/AutoSizingMenu.qml @@ -5,7 +5,6 @@ import QtQuick import QtQuick.Controls -import Style Menu { popupType: Popup.Window diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bbfd290ec6c14..217a7d6a1f172 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -101,6 +101,10 @@ nextcloud_add_test(UnifiedSearchListmodel) nextcloud_add_test(ActivityListModel) nextcloud_add_test(SortedActivityListModel) nextcloud_add_test(ActivityData) +add_test(NAME ActivityFileMenuQmlTest + COMMAND Qt6::qmltestrunner + -input "${CMAKE_CURRENT_SOURCE_DIR}/qml/activityfilemenu/testactivityfilemenu.qml" +) nextcloud_add_test(TalkReply) nextcloud_add_test(LockFile) nextcloud_add_test(ShareModel) diff --git a/test/qml/activityfilemenu/testactivityfilemenu.qml b/test/qml/activityfilemenu/testactivityfilemenu.qml new file mode 100644 index 0000000000000..84dc2db2b2770 --- /dev/null +++ b/test/qml/activityfilemenu/testactivityfilemenu.qml @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtTest +import "../../../src/gui/activity/qml" + +Item { + id: testRoot + + width: 200 + height: 100 + + TestCase { + id: testCase + + name: "ActivityFileMenu" + when: windowShown + + property ActivityFileMenuButton button + + SignalSpy { + id: fileDetailsRequestedSpy + signalName: "fileDetailsRequested" + } + + SignalSpy { + id: fileActionsRequestedSpy + signalName: "fileActionsRequested" + } + + Component { + id: buttonComponent + + ActivityFileMenuButton { + filePath: "/sync/folder/file.txt" + serverHasIntegration: true + itemFontPixelSize: 14 + buttonWidth: 44 + buttonHeight: 32 + buttonIconSize: 16 + } + } + + function init() + { + button = createTemporaryObject(buttonComponent, testRoot); + verify(button); + + fileDetailsRequestedSpy.target = button; + fileActionsRequestedSpy.target = button; + } + + function test_contentFitsWithoutScrolling() + { + button.menu.popup(); + tryCompare(button.menu, "opened", true); + compare(button.menu.count, 2); + const lastItem = button.menu.itemAt(button.menu.count - 1); + verify(lastItem); + verify(button.menu.availableHeight >= lastItem.y + lastItem.height); + } + + function openMenu() + { + mouseClick(button); + tryCompare(button.menu, "opened", true); + } + + function test_buttonOpensMenu() + { + verify(!button.menu.opened); + openMenu(); + verify(button.menu.opened); + } + + function test_fileDetailsItemEmitsCapturedPath() + { + openMenu(); + + const fileDetailsItem = button.menu.itemAt(0); + verify(fileDetailsItem); + mouseClick(fileDetailsItem); + + compare(fileDetailsRequestedSpy.count, 1); + compare(fileDetailsRequestedSpy.signalArguments[0][0], "/sync/folder/file.txt"); + } + + function test_fileActionsItemEmitsCapturedPath() + { + openMenu(); + + const fileActionsItem = button.menu.itemAt(1); + verify(fileActionsItem); + mouseClick(fileActionsItem); + + compare(fileActionsRequestedSpy.count, 1); + compare(fileActionsRequestedSpy.signalArguments[0][0], "/sync/folder/file.txt"); + } + } +}