Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/macos-build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
<file>src/gui/tray/TrayWindowHeader.qml</file>
<file>src/gui/activity/qml/ActivityItemContextMenu.qml</file>
<file>src/gui/activity/qml/ActivityItemActions.qml</file>
<file>src/gui/activity/qml/ActivityFileMenu.qml</file>
<file>src/gui/activity/qml/ActivityFileMenuButton.qml</file>
<file>src/gui/activity/qml/ActivityItemContent.qml</file>
<file>src/gui/activity/qml/TalkReplyTextField.qml</file>
<file>src/gui/tray/CallNotificationDialog.qml</file>
Expand Down
46 changes: 46 additions & 0 deletions src/gui/activity/qml/ActivityFileMenu.qml
Original file line number Diff line number Diff line change
@@ -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
}
}
51 changes: 51 additions & 0 deletions src/gui/activity/qml/ActivityFileMenuButton.qml
Original file line number Diff line number Diff line change
@@ -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)
}
}
49 changes: 10 additions & 39 deletions src/gui/activity/qml/ActivityItemContent.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion src/gui/tray/AutoSizingMenu.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import QtQuick
import QtQuick.Controls
import Style

Menu {
popupType: Popup.Window
Expand Down
4 changes: 4 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
103 changes: 103 additions & 0 deletions test/qml/activityfilemenu/testactivityfilemenu.qml
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
Loading