diff --git a/.github/workflows/code_style.yml b/.github/workflows/code_style.yml index 19e3d2f6e..6822fbbc7 100644 --- a/.github/workflows/code_style.yml +++ b/.github/workflows/code_style.yml @@ -101,3 +101,59 @@ jobs: fi echo "OK: No direct StackView usage found." + + no_listview: + name: No direct ListView usage in QML + if: ( github.repository == 'MerginMaps/mobile' ) && (!contains(github.event.head_commit.message, 'Translate ')) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check for direct ListView instantiation in QML files + run: | + # MMListView.qml is the approved wrapper — exclude it from the search. + # Any other QML file with a bare "ListView" is using the raw Qt component + # instead of the wrapper, which breaks Squish test support. + matches=$(grep -rn --include="*.qml" \ + --exclude="MMListView.qml" \ + '\ colors @@ -21,8 +21,8 @@ ScrollView { signal activeColorChangeRequested( color newColor ) height: scrollRow.height + contentWidth: scrollRow.width ScrollBar.vertical.policy: ScrollBar.AlwaysOff - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff Row { id: scrollRow diff --git a/app/qml/components/MMListView.qml b/app/qml/components/MMListView.qml index d1d26e95b..13a1f5202 100644 --- a/app/qml/components/MMListView.qml +++ b/app/qml/components/MMListView.qml @@ -8,16 +8,74 @@ ***************************************************************************/ import QtQuick +import QtQuick.Controls -ListView { +// Drop-in replacement for ListView that also shows a scrollbar on desktop. +// Rooted in a plain Item rather than ScrollView, so only the properties +// aliased below are exposed - ScrollView/Pane/Control have their own +// contentWidth/spacing/padding that would otherwise shadow ListView's. +Item { id: root - // when flicking up really fast, we should go back to the first item - onVerticalOvershootChanged: { - if (verticalOvershoot < -200) { - root.contentY= -root.topMargin - root.returnToBounds(); + + property alias model: listView.model + property alias delegate: listView.delegate + property alias header: listView.header + property alias footer: listView.footer + property alias section: listView.section + property alias add: listView.add + property alias addDisplaced: listView.addDisplaced + + property alias orientation: listView.orientation + property alias spacing: listView.spacing + property alias interactive: listView.interactive + property alias maximumFlickVelocity: listView.maximumFlickVelocity + property alias topMargin: listView.topMargin + property alias bottomMargin: listView.bottomMargin + + property alias currentIndex: listView.currentIndex + property alias count: listView.count + + property alias contentY: listView.contentY + property alias contentWidth: listView.contentWidth + property alias contentHeight: listView.contentHeight + property alias atYEnd: listView.atYEnd + + implicitWidth: scrollView.implicitWidth + implicitHeight: scrollView.implicitHeight + + function positionViewAtIndex( index, mode ) { + listView.positionViewAtIndex( index, mode ) + } + + function positionViewAtEnd() { + listView.positionViewAtEnd() + } + + ScrollView { + id: scrollView + + anchors.fill: parent + + // reserve room for the scrollbar so content isn't drawn under it + rightPadding: ScrollBar.vertical.visible ? ScrollBar.vertical.width * 2 : 0 + bottomPadding: ScrollBar.horizontal.visible ? ScrollBar.horizontal.height * 2 : 0 + + // non-interactive lists can't be scrolled, so never show a scrollbar for them + ScrollBar.vertical.policy: listView.interactive && !__inputUtils.isMobilePlatform() && listView.orientation === ListView.Vertical && listView.contentHeight > listView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + ScrollBar.horizontal.policy: listView.interactive && !__inputUtils.isMobilePlatform() && listView.orientation === ListView.Horizontal && listView.contentWidth > listView.width ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + + ListView { + id: listView + + // when flicking up really fast, we should go back to the first item + onVerticalOvershootChanged: { + if (verticalOvershoot < -200) { + listView.contentY = -listView.topMargin + listView.returnToBounds(); + } + } + delegateModelAccess: DelegateModel.ReadWrite } } - delegateModelAccess: DelegateModel.ReadWrite -} \ No newline at end of file +} diff --git a/app/qml/components/MMScrollView.qml b/app/qml/components/MMScrollView.qml index 5ea4c2dea..01b60682e 100644 --- a/app/qml/components/MMScrollView.qml +++ b/app/qml/components/MMScrollView.qml @@ -16,8 +16,10 @@ import QtQuick.Controls ScrollView { id: root + rightPadding: ScrollBar.vertical.visible ? ScrollBar.vertical.width * 2 : 0 + contentWidth: availableWidth // to only scroll vertically - ScrollBar.vertical.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: !__inputUtils.isMobilePlatform() && root.contentHeight > root.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff } diff --git a/app/qml/components/MMToolbar.qml b/app/qml/components/MMToolbar.qml index 3e1568fca..2ba33e54c 100644 --- a/app/qml/components/MMToolbar.qml +++ b/app/qml/components/MMToolbar.qml @@ -151,6 +151,11 @@ Rectangle { toolbarModel.clear() menuModel.clear() + // defer so old delegates finish being removed before new ones are added + Qt.callLater( root.populateToolbar ) + } + + function populateToolbar() { // find how many visible buttons we need to place to toolbar or menu let visibleButtonsCount = 0 for ( let i = 0; i < root.model.count; ++i ) { diff --git a/app/qml/form/editors/MMFormGalleryEditor.qml b/app/qml/form/editors/MMFormGalleryEditor.qml index 37221230b..a3409fef6 100644 --- a/app/qml/form/editors/MMFormGalleryEditor.qml +++ b/app/qml/form/editors/MMFormGalleryEditor.qml @@ -54,7 +54,9 @@ MMPrivateComponents.MMBaseInput { } delegate: MMComponents.MMPhotoCard{ - size: rowView.height + // fixed margin, not live scrollbar padding - these are square cards, so + // shrinking height also shrinks width, risking an infinite resize loop + size: rowView.height - __style.margin12 imageSource: { let absolutePath = model.PhotoPath @@ -82,9 +84,10 @@ MMPrivateComponents.MMBaseInput { id: addFeatureComponent Row { + height: rowView.height - __style.margin12 Rectangle { - height: rowView.height + height: parent.height width: height radius: __style.radius20 diff --git a/app/qml/gps/MMSelectionDrawer.qml b/app/qml/gps/MMSelectionDrawer.qml index dbc280bd9..f6f86b281 100644 --- a/app/qml/gps/MMSelectionDrawer.qml +++ b/app/qml/gps/MMSelectionDrawer.qml @@ -91,12 +91,11 @@ MMComponents.MMDrawer { } } - ScrollView { + MMComponents.MMScrollView { width: parent.width height: scrollRow.height ScrollBar.vertical.policy: ScrollBar.AlwaysOff - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.interactive: true contentHeight: scrollRow.height diff --git a/app/qml/gps/MMStakeoutDrawer.qml b/app/qml/gps/MMStakeoutDrawer.qml index c932ea260..ad1eab777 100644 --- a/app/qml/gps/MMStakeoutDrawer.qml +++ b/app/qml/gps/MMStakeoutDrawer.qml @@ -118,11 +118,10 @@ MMDrawer { visible: distanceState.state === "closeRange" } - ScrollView { + MMScrollView { id: gpsScrollView width: parent.width - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.vertical.policy: ScrollBar.AlwaysOff visible: distanceState.state === "closeRange" diff --git a/app/qml/project/MMProjectStatusPage.qml b/app/qml/project/MMProjectStatusPage.qml index 295d1be3e..d20826f0f 100644 --- a/app/qml/project/MMProjectStatusPage.qml +++ b/app/qml/project/MMProjectStatusPage.qml @@ -105,6 +105,8 @@ MMComponents.MMPage { /* Table name within single file */ id: mainText + Layout.fillWidth: true + text:itemText font: __style.p6 color: __style.nightColor @@ -117,7 +119,7 @@ MMComponents.MMPage { /* Added rows for table */ id: addedItem - width: delegateItem.width + Layout.fillWidth: true count: inserts visible: inserts > 0 type: MM.MerginProjectStatusModel.Added @@ -126,7 +128,7 @@ MMComponents.MMPage { MMProjectComponents.MMProjectStatusItem { /* Edited rows for table */ id: editedItem - width: delegateItem.width + Layout.fillWidth: true count: updates visible: updates > 0 type: MM.MerginProjectStatusModel.Updated @@ -136,7 +138,7 @@ MMComponents.MMPage { /* Deleted rows for table */ id: deletedItem - width: delegateItem.width + Layout.fillWidth: true count: deletes visible: deletes > 0 type: MM.MerginProjectStatusModel.Deleted diff --git a/app/qml/project/components/MMProjectDelegate.qml b/app/qml/project/components/MMProjectDelegate.qml index b858b0666..54d3bcbab 100644 --- a/app/qml/project/components/MMProjectDelegate.qml +++ b/app/qml/project/components/MMProjectDelegate.qml @@ -38,6 +38,9 @@ Control { height: implicitHeight + // shrink to leave room for the list's vertical scrollbar, so content isn't drawn underneath it + implicitWidth: ListView.view.width + topPadding: __style.margin20 rightPadding: __style.margin20 leftPadding: __style.margin20 diff --git a/app/qml/settings/MMAboutPage.qml b/app/qml/settings/MMAboutPage.qml index de5bca609..dbf4c607c 100644 --- a/app/qml/settings/MMAboutPage.qml +++ b/app/qml/settings/MMAboutPage.qml @@ -20,14 +20,12 @@ MMPage { pageHeader.title: qsTr( "About" ) - pageContent: ScrollView { + pageContent: MMScrollView { width: parent.width height: parent.height - contentWidth: availableWidth // to only scroll vertically ScrollBar.vertical.policy: ScrollBar.AlwaysOff - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ColumnLayout { width: parent.width