From 8060dc843e4c8c7dc3d63328cb3959471766a97e Mon Sep 17 00:00:00 2001 From: PeterPetrik Date: Thu, 4 Apr 2024 16:05:24 +0200 Subject: [PATCH 01/22] set maxheigh for mapthemes --- app/qml/main.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/qml/main.qml b/app/qml/main.qml index 534743b00..5e788fec3 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -493,7 +493,7 @@ ApplicationWindow { MMMapThemeDrawer { id: mapThemesPanel - height: ( window.height / 2 ) + maxHeight: ( window.height / 2 ) width: window.width edge: Qt.BottomEdge From 63c251fe82d2f21b34368849d21fc84fbfb31851 Mon Sep 17 00:00:00 2001 From: PeterPetrik Date: Thu, 4 Apr 2024 16:13:52 +0200 Subject: [PATCH 02/22] allow signals from empty state list delegate even when it is not scrollable --- app/qml/components/MMListDrawer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/qml/components/MMListDrawer.qml b/app/qml/components/MMListDrawer.qml index 6d4bac4db..807ec9636 100644 --- a/app/qml/components/MMListDrawer.qml +++ b/app/qml/components/MMListDrawer.qml @@ -30,7 +30,7 @@ MMDrawer { width: parent.width height: Math.min( root.drawerContentAvailableHeight, contentHeight ) - enabled: contentHeight > height + enabled: emptyStateDelegateLoader.visible Loader { id: emptyStateDelegateLoader From df120297d1fd0215f389fc2fccb68ec81022cfa6 Mon Sep 17 00:00:00 2001 From: PeterPetrik Date: Wed, 10 Apr 2024 17:29:27 +0200 Subject: [PATCH 03/22] allow links in form text editors and preview panels --- app/qml/form/MMPreviewDrawer.qml | 8 ++++++++ app/qml/form/editors/MMFormTextEditor.qml | 2 +- app/qml/form/editors/MMFormTextMultilineEditor.qml | 4 ++-- app/qml/inputs/MMTextInput.qml | 10 ++++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/qml/form/MMPreviewDrawer.qml b/app/qml/form/MMPreviewDrawer.qml index 83a7f464a..656213926 100644 --- a/app/qml/form/MMPreviewDrawer.qml +++ b/app/qml/form/MMPreviewDrawer.qml @@ -242,6 +242,10 @@ Item { elide: Text.ElideRight wrapMode: Text.NoWrap + + onLinkActivated: function ( link ) { + Qt.openUrlExternally( link ) + } } MMComponents.MMListSpacer { height: __style.margin8 } @@ -272,6 +276,10 @@ Item { elide: Text.ElideRight text: root.controller.html + + onLinkActivated: function ( link ) { + Qt.openUrlExternally( link ) + } } } diff --git a/app/qml/form/editors/MMFormTextEditor.qml b/app/qml/form/editors/MMFormTextEditor.qml index 4b2697111..68122a5e6 100644 --- a/app/qml/form/editors/MMFormTextEditor.qml +++ b/app/qml/form/editors/MMFormTextEditor.qml @@ -45,7 +45,7 @@ MMTextInput { showClearIcon: false - enabled: !_fieldIsReadOnly + readOnly: _fieldIsReadOnly textFieldComponent.readOnly: _fieldIsReadOnly textFieldComponent.inputMethodHints: root._field.isNumeric ? Qt.ImhFormattedNumbersOnly : Qt.ImhNone textFieldComponent.color: __style.nightColor diff --git a/app/qml/form/editors/MMFormTextMultilineEditor.qml b/app/qml/form/editors/MMFormTextMultilineEditor.qml index e7882b2db..2627573d1 100644 --- a/app/qml/form/editors/MMFormTextMultilineEditor.qml +++ b/app/qml/form/editors/MMFormTextMultilineEditor.qml @@ -50,8 +50,6 @@ MMBaseInput { warningMsg: _fieldWarningMessage errorMsg: _fieldErrorMessage - enabled: !_fieldIsReadOnly - hasFocus: textArea.activeFocus hasCheckbox: _fieldRememberValueSupported @@ -76,6 +74,8 @@ MMBaseInput { height: contentHeight + textArea.verticalPadding width: parent.width + readOnly: root._fieldIsReadOnly + text: root.text textFormat: root._fieldConfig['UseHtml'] ? TextEdit.RichText : TextEdit.PlainText diff --git a/app/qml/inputs/MMTextInput.qml b/app/qml/inputs/MMTextInput.qml index cd61c13c2..2b030704c 100644 --- a/app/qml/inputs/MMTextInput.qml +++ b/app/qml/inputs/MMTextInput.qml @@ -14,7 +14,9 @@ import "../components" /* * Common text input to use in the app. - * Disabled state can be achieved by setting `enabled: false`. + * + * Disabled state can be achieved by setting `enabled: false` + * ReadOnly state can be achieved by setting `readOnly: true` * * See MMBaseInput for more properties. */ @@ -25,7 +27,7 @@ MMBaseInput { property bool showClearIcon: true property alias text: textField.text property alias placeholderText: textField.placeholderText - + property alias readOnly: textField.readOnly property alias textFieldComponent: textField signal textEdited( string text ) @@ -38,7 +40,7 @@ MMBaseInput { anchors.fill: parent placeholderTextColor: __style.nightAlphaColor - color: root.enabled ? __style.nightColor : __style.mediumGreenColor + color: root.enabled && !readOnly ? __style.nightColor : __style.mediumGreenColor font: __style.p5 hoverEnabled: true @@ -57,7 +59,7 @@ MMBaseInput { size: __style.icon24 source: __style.closeIcon - color: root.enabled ? __style.forestColor : __style.mediumGreenColor + color: root.enabled && !readOnly ? __style.forestColor : __style.mediumGreenColor visible: root.showClearIcon && textField.activeFocus && textField.text.length > 0 } From 238338bcad583b5959abc6ce452f9e559cce468f Mon Sep 17 00:00:00 2001 From: PeterPetrik Date: Fri, 12 Apr 2024 14:20:33 +0200 Subject: [PATCH 04/22] overflow text for long photo names --- app/qml/project/components/MMProjectStatusItem.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/qml/project/components/MMProjectStatusItem.qml b/app/qml/project/components/MMProjectStatusItem.qml index 15d577253..cc97a834d 100644 --- a/app/qml/project/components/MMProjectStatusItem.qml +++ b/app/qml/project/components/MMProjectStatusItem.qml @@ -51,6 +51,7 @@ Rectangle { text: internal.text font: __style.t4 color: internal.fgColor + elide: Text.ElideMiddle Layout.fillWidth: true } From 6e607aa06d55b13f4ec46138875fd93bfe8fa742 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 12 Apr 2024 14:41:35 +0200 Subject: [PATCH 05/22] Fix logic of how the "N more" is calculated in relation editor --- app/qml/form/editors/MMFormRelationEditor.qml | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/app/qml/form/editors/MMFormRelationEditor.qml b/app/qml/form/editors/MMFormRelationEditor.qml index a1f1fc9ed..93a89c791 100644 --- a/app/qml/form/editors/MMFormRelationEditor.qml +++ b/app/qml/form/editors/MMFormRelationEditor.qml @@ -40,8 +40,8 @@ MMInputs.MMBaseInput { contentItemHeight: privates.itemHeight * privates.rows + 2 * flow.spacing + 20 * __dp - Component.onCompleted: root.recalculate() - onWidthChanged: root.recalculate() + Component.onCompleted: root.recalculateVisibleItems() + onWidthChanged: root.recalculateVisibleItems() title: _fieldShouldShowTitle ? _fieldTitle : "" @@ -93,7 +93,10 @@ MMInputs.MMBaseInput { // Repeater does not necesarry clear delegates immediately if they are invisible, // we need to do hard reload in this case so that recalculateVisibleItems() is triggered - root.recalculate() + repeater.model = null + repeater.model = rmodel + + root.recalculateVisibleItems() } } @@ -120,11 +123,7 @@ MMInputs.MMBaseInput { onClicked: root.openLinkedFeature( model.FeaturePair ) } - onVisibleChanged: { - if(!visible) { - repeater.invisibleIds++ - } - } + onVisibleChanged: root.recalculateVisibleItems() } } @@ -161,10 +160,17 @@ MMInputs.MMBaseInput { } } - function recalculate() { - repeater.invisibleIds = 0 - repeater.model = null - repeater.model = rmodel + function recalculateVisibleItems() { + let invisibles_count = 0 + + for ( let i = 0; i < repeater.count; i++ ) { + let delegate_i = repeater.itemAt( i ) + if ( delegate_i && !delegate_i.visible ) { + invisibles_count++ + } + } + + repeater.invisibleIds = invisibles_count } Loader { From 33cfc2a32c33e8dba816b77c99c283da0f67e53f Mon Sep 17 00:00:00 2001 From: PeterPetrik Date: Fri, 12 Apr 2024 14:48:26 +0200 Subject: [PATCH 06/22] go to map view in case project status was started from it --- app/qml/main.qml | 4 ++-- app/qml/project/MMProjectController.qml | 32 +++++++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/qml/main.qml b/app/qml/main.qml index 7663ab904..6f62f7756 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -221,7 +221,7 @@ ApplicationWindow { onLocalChangesPanelRequested: { stateManager.state = "projects" - projectController.openChangesPanel( __activeProject.projectFullName() ) + projectController.openChangesPanel( __activeProject.projectFullName(), true ) } onOpenTrackingPanel: { @@ -344,7 +344,7 @@ ApplicationWindow { iconSource: __style.localChangesIcon onClicked: { stateManager.state = "projects" - projectController.openChangesPanel( __activeProject.projectFullName() ) + projectController.openChangesPanel( __activeProject.projectFullName(), true ) } } diff --git a/app/qml/project/MMProjectController.qml b/app/qml/project/MMProjectController.qml index ae92ca745..a05e5fe86 100644 --- a/app/qml/project/MMProjectController.qml +++ b/app/qml/project/MMProjectController.qml @@ -68,13 +68,13 @@ Item { accountController.start() } - function openChangesPanel( projectId ) + function openChangesPanel( projectId, closeOnBack ) { - stackView.push( statusPageComp, {hasChanges: __merginProjectStatusModel.loadProjectInfo( projectId )} ) - } - - function showChanges( projectId ) { - root.openChangesPanel( projectId ) + stackView.push( statusPageComp, { + hasChanges: __merginProjectStatusModel.loadProjectInfo( projectId ), + closeOnBack: closeOnBack + } + ) } function showSelectWorkspacePage() { @@ -295,7 +295,7 @@ Item { setupProjectOpen( projectFilePath ) } onShowLocalChangesRequested: function( projectId ) { - showChanges( projectId ) + root.openChangesPanel( projectId, false ) } list.onActiveProjectDeleted: setupProjectOpen( "" ) @@ -329,7 +329,7 @@ Item { setupProjectOpen( projectFilePath ) } onShowLocalChangesRequested: function( projectId ) { - showChanges( projectId ) + root.openChangesPanel( projectId, false ) } list.onActiveProjectDeleted: setupProjectOpen( "" ) } @@ -345,7 +345,7 @@ Item { setupProjectOpen( projectFilePath ) } onShowLocalChangesRequested: function( projectId ) { - showChanges( projectId ) + root.openChangesPanel( projectId, false ) } list.onActiveProjectDeleted: function() { setupProjectOpen( "" ) @@ -484,9 +484,21 @@ Item { id: statusPageComp MMProjectStatusPage { id: statusPage + + // Close project controller when back is clicked + // e.g. when project changes are requested from + // map view + property bool closeOnBack: false + height: root.height width: root.width - onBack: stackView.popOnePageOrClose() + onBack: { + if (closeOnBack) { + root.hidePanel() + } else { + stackView.popOnePageOrClose() + } + } } } From 8b1e54e3e44e8b5597808dc44ede1d600f8a34a4 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 12 Apr 2024 16:56:59 +0200 Subject: [PATCH 07/22] Try Qt 6.6.3 --- .github/workflows/android.yml | 2 +- .github/workflows/code_style.yml | 2 +- .github/workflows/gallery.yml | 2 +- .github/workflows/i18n.yml | 2 +- .github/workflows/ios.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/win.yml | 2 +- CMakeLists.txt | 2 +- INSTALL.md | 10 +++++----- scripts/update_qt_version.bash | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d9009c308..e3fb5307d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -23,7 +23,7 @@ jobs: if: ( github.repository == 'MerginMaps/mobile' ) && (!contains(github.event.head_commit.message, 'Translate ')) runs-on: macos-13 env: - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change NDK_VERSION: r25 NDK_VERSION_FULL: r25b JDK_VERSION: 11 diff --git a/.github/workflows/code_style.yml b/.github/workflows/code_style.yml index cfb3621fa..5d48475c5 100644 --- a/.github/workflows/code_style.yml +++ b/.github/workflows/code_style.yml @@ -18,7 +18,7 @@ on: - published env: - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change CACHE_VERSION: 0 jobs: diff --git a/.github/workflows/gallery.yml b/.github/workflows/gallery.yml index 74a626b8d..947ad28d3 100644 --- a/.github/workflows/gallery.yml +++ b/.github/workflows/gallery.yml @@ -24,7 +24,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-2019] runs-on: ${{ matrix.os }} env: - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change GITHUB_TOKEN: ${{ secrets.INPUTAPP_BOT_GITHUB_TOKEN }} CACHE_VERSION: 0 XC_VERSION: ${{ '14.2' }} # macos-only diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index 59c7755b0..072537da8 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -11,7 +11,7 @@ jobs: if: ( github.repository == 'MerginMaps/mobile' ) && (!contains(github.event.head_commit.message, 'Translate ')) runs-on: ubuntu-latest env: - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 6ae47124a..142f8cd76 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -16,7 +16,7 @@ on: - published env: - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change XC_VERSION: ${{ '15.2' }} IOS_CMAKE_TOOLCHAIN_VERSION: "4.4.0" INPUT_SDK_VERSION: arm64-ios-20240405-173 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9a9721273..8ef9752f8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -18,7 +18,7 @@ on: env: CCACHE_DIR: ~/.ccache INPUT_SDK_VERSION: x64-linux-20240405-176 - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change CACHE_VERSION: 3 concurrency: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 8c0518ff1..acaa2702a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,7 +16,7 @@ on: - published env: - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change INPUT_SDK_VERSION: x64-osx-20240405-195 CCACHE_DIR: /Users/runner/work/ccache CACHE_VERSION: 1 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 4b85f0fdb..2c185dade 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -25,7 +25,7 @@ jobs: runs-on: windows-2019 env: - QT_VERSION: '6.6.0' # use scripts/update_qt_version.bash to change + QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change INPUT_SDK_VERSION: x64-windows-20240405-223 CCACHE_DIR: C:/ccache-cache # https://linux.die.net/man/1/ccache CACHE_VERSION: 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index a81cedd7b..642f56db4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.22) set(MM_VERSION_MAJOR "2024") set(MM_VERSION_MINOR "1") set(MM_VERSION_PATCH "0") -set(QT_VERSION_DEFAULT "6.6.0") +set(QT_VERSION_DEFAULT "6.6.3") # Note: we cannot set this for non-android build, since CMake will start looking for # Qt6AndroidMacros.cmake diff --git a/INSTALL.md b/INSTALL.md index 2960bfd42..b5b49ea2f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -140,7 +140,7 @@ Steps to build and run Input: mkdir build cd build cmake -G Ninja \ - -DCMAKE_PREFIX_PATH=~/Qt/6.6.0/gcc_64 \ + -DCMAKE_PREFIX_PATH=~/Qt/6.6.3/gcc_64 \ -DINPUT_SDK_PATH=~/input-sdk/x64-linux \ -DQGIS_QUICK_DATA_PATH=~/input/app/android/assets/qgis-data \ -DUSE_MM_SERVER_API_KEY=FALSE \ @@ -296,8 +296,8 @@ Now you can create a build (either on commmand line or by setting these variable cmake \ -DIOS=TRUE \ - -DCMAKE_PREFIX_PATH=/opt/Qt/6.6.0/ios \ - -DQT_HOST_PATH=/opt/Qt/6.6.0/macos \ + -DCMAKE_PREFIX_PATH=/opt/Qt/6.6.3/ios \ + -DQT_HOST_PATH=/opt/Qt/6.6.3/macos \ -DCMAKE_TOOLCHAIN_FILE:PATH="~/input-sdk/ios.toolchain.cmake" \ -DCMAKE_INSTALL_PREFIX:PATH="../install" \ -DUSE_SERVER_API_KEY=FALSE \ @@ -333,7 +333,7 @@ export BASE_DIR=~/Projects/quick; cmake \ -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_PREFIX_PATH=/opt/Qt/6.6.0/macos \ + -DCMAKE_PREFIX_PATH=/opt/Qt/6.6.3/macos \ -DCMAKE_INSTALL_PREFIX:PATH=$BASE_DIR/install-macos \ -DINPUT_SDK_PATH=$BASE_DIR/sdk/x64-osx \ -GNinja \ @@ -361,7 +361,7 @@ For version of the tools used, see `.github/workflows/win.yml` - setup build environment ``` set ROOT_DIR=C:\Users\zilol\Projects -set Qt6_DIR=C:\Qt\6.6.0\msvc2019_64 +set Qt6_DIR=C:\Qt\6.6.3\msvc2019_64 set PATH=%QT_ROOT%\bin;C:\Program Files\CMake\bin\;%PATH% "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat" -arch=x64 ``` diff --git a/scripts/update_qt_version.bash b/scripts/update_qt_version.bash index 3341d6d1e..1af275fdb 100755 --- a/scripts/update_qt_version.bash +++ b/scripts/update_qt_version.bash @@ -45,7 +45,7 @@ for FNAME in \ do echo "patching $FNAME" # e.g. QT_VERSION: '6.5.2' - sed -i.orig -E "s|QT_VERSION: \'[0-9]+.[0-9]+.[0-9]+\'|QT_VERSION: \'${VERSION}\'|g" $FNAME + sed -i.orig -E "s|QT_VERSION: '[0-9]+.[0-9]+.[0-9]+'|QT_VERSION: '${VERSION}'|g" $FNAME rm -f $FNAME.orig done From 4696aab6165723e881e962cd25810f01ea206efc Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 15 Apr 2024 12:57:48 +0200 Subject: [PATCH 08/22] Fix feature form width when tab bar has many tabs Fixes #3291 --- app/qml/form/MMFormPage.qml | 2 +- gallery/qml/pages/FormPage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/qml/form/MMFormPage.qml b/app/qml/form/MMFormPage.qml index e263f18ce..91ab44e30 100644 --- a/app/qml/form/MMFormPage.qml +++ b/app/qml/form/MMFormPage.qml @@ -109,7 +109,7 @@ Page { id: tabBar Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: __style.maxPageWidth + Layout.maximumWidth: Math.min(__style.maxPageWidth, root.width) visible: root.controller.hasTabs diff --git a/gallery/qml/pages/FormPage.qml b/gallery/qml/pages/FormPage.qml index 5a2a66ef3..615b33102 100644 --- a/gallery/qml/pages/FormPage.qml +++ b/gallery/qml/pages/FormPage.qml @@ -48,7 +48,7 @@ Page { id: tabBar Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: __style.maxPageWidth + Layout.maximumWidth: Math.min(__style.maxPageWidth, root.width) tabButtonsModel: ListModel { id: tabModel From 2b3411471e4464f0cabbb944caa96917a382355f Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 15 Apr 2024 16:11:53 +0200 Subject: [PATCH 09/22] Add welcome to new design dialog It gets shown upon start, but only first time the app is run after upgrade from < 2024.x.y release. --- app/main.cpp | 14 ++++++++++ app/qml/CMakeLists.txt | 1 + .../dialogs/MMWelcomeToNewDesignDialog.qml | 27 +++++++++++++++++++ app/qml/main.qml | 9 +++++++ gallery/qml/pages/DrawerPage.qml | 12 +++++++++ 5 files changed, 63 insertions(+) create mode 100644 app/qml/dialogs/MMWelcomeToNewDesignDialog.qml diff --git a/app/main.cpp b/app/main.cpp index 37759b413..00eceb9de 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -719,6 +719,20 @@ int main( int argc, char *argv[] ) #endif engine.rootContext()->setContextProperty( "__use_simulated_position", use_simulated_position ); + // show "new look and feel" welcome dialog on start? + QString lastAppVersion = as.appVersion(); + bool showWelcomeToNewDesignDialog = false; + if ( !lastAppVersion.isEmpty() ) // this is not a first run? + { + int dotPos = lastAppVersion.indexOf( '.' ); + if ( dotPos >= 0 && lastAppVersion.left( dotPos ).toInt() < 2024 ) + { + // only show if previously we were on 2.x version, and now we're on 2024.x.y with the new design + showWelcomeToNewDesignDialog = true; + } + } + engine.rootContext()->setContextProperty( "__showWelcomeToNewDesignDialog", showWelcomeToNewDesignDialog ); + QQmlComponent component( &engine, QUrl( "qrc:/com.merginmaps/imports/MMInput/main.qml" ) ); QObject *object = component.create(); diff --git a/app/qml/CMakeLists.txt b/app/qml/CMakeLists.txt index 3dfaf83bf..2daa4a974 100644 --- a/app/qml/CMakeLists.txt +++ b/app/qml/CMakeLists.txt @@ -61,6 +61,7 @@ set(MM_QML dialogs/MMPositionTrackingDialog.qml dialogs/MMProjectLimitDialog.qml dialogs/MMRemoveProjectDialog.qml + dialogs/MMWelcomeToNewDesignDialog.qml dialogs/MMSplittingFailedDialog.qml dialogs/MMStorageLimitDialog.qml dialogs/MMStreamingModeDialog.qml diff --git a/app/qml/dialogs/MMWelcomeToNewDesignDialog.qml b/app/qml/dialogs/MMWelcomeToNewDesignDialog.qml new file mode 100644 index 000000000..811d1ae2f --- /dev/null +++ b/app/qml/dialogs/MMWelcomeToNewDesignDialog.qml @@ -0,0 +1,27 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +import QtQuick +import QtQuick.Controls + +import "../components" +import "../inputs" + +MMDrawerDialog { + id: root + + imageSource: __style.positiveMMSymbolImage + title: qsTr( "New Look & Feel" ) + description: qsTr( "We've been busy making Mergin Maps even better! This update brings a fresh look and improved navigation, making it faster to find what you need. Take a look around!" ) + primaryButton.text: qsTr("Let's start!") + + onPrimaryButtonClicked: { + close() + } +} diff --git a/app/qml/main.qml b/app/qml/main.qml index 7663ab904..aca929812 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -773,6 +773,15 @@ ApplicationWindow { } } + MMWelcomeToNewDesignDialog { + id: welcomeToNewDesignDialog + + Component.onCompleted: { + if ( __showWelcomeToNewDesignDialog ) + open() + } + } + Connections { target: __syncManager enabled: stateManager.state === "map" diff --git a/gallery/qml/pages/DrawerPage.qml b/gallery/qml/pages/DrawerPage.qml index a985f7e75..e3400a423 100644 --- a/gallery/qml/pages/DrawerPage.qml +++ b/gallery/qml/pages/DrawerPage.qml @@ -186,6 +186,14 @@ Page { removeReceiverDialog.open( testModelItem.providerId ) } } + + Button { + text: "welcomeToNewDesignDialog" + + onClicked: { + welcomeToNewDesignDialog.open() + } + } } } @@ -355,4 +363,8 @@ Page { visible = true } } + + MMWelcomeToNewDesignDialog { + id: welcomeToNewDesignDialog + } } From cdd5f732690b5ab48efa3ced7ecc629d6323292b Mon Sep 17 00:00:00 2001 From: PeterPetrik Date: Mon, 15 Apr 2024 18:39:51 +0200 Subject: [PATCH 10/22] use qt663 SDK --- .github/workflows/android.yml | 41 ++++++++++------------------------- .github/workflows/ios.yml | 41 ++++++++++------------------------- .github/workflows/linux.yml | 12 +++++++--- .github/workflows/macos.yml | 10 +++++++-- .github/workflows/win.yml | 12 +++++++--- 5 files changed, 49 insertions(+), 67 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index e3fb5307d..4079c170e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -29,11 +29,12 @@ jobs: JDK_VERSION: 11 SDK_PLATFORM: android-33 SDK_BUILD_TOOLS: 33.0.1 - INPUT_SDK_VERSION_ARM: arm-android-20240405-212 - INPUT_SDK_VERSION_ARM64: arm64-android-20240405-212 + INPUT_SDK_VERSION_ARM: arm-android-20240415-218 + INPUT_SDK_VERSION_ARM64: arm64-android-20240415-218 CCACHE_DIR: /Users/runner/work/ccache GITHUB_TOKEN: ${{ secrets.INPUTAPP_BOT_GITHUB_TOKEN }} - CACHE_VERSION: 4 + CACHE_VERSION: 0 + CMAKE_VERSION: '3.29.0' QT_ANDROID_KEYSTORE_ALIAS: input QT_ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.INPUTKEYSTORE_STOREPASS }} QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.INPUTKEYSTORE_STOREPASS }} @@ -80,32 +81,8 @@ jobs: - name: Install Build Dependencies run: | - # FIXME: We should not need to do this, but there are currently some complications with Github CI. - # See https://github.com/actions/runner-images/issues/4020. - # See https://github.com/actions/runner-images/issues/8838. - - rm -f '/usr/local/bin/2to3' - rm -f '/usr/local/bin/2to3-3.11' - rm -f '/usr/local/bin/2to3-3.12' - rm -f '/usr/local/bin/idle3' - rm -f '/usr/local/bin/idle3.11' - rm -f '/usr/local/bin/idle3.12' - rm -f '/usr/local/bin/pydoc3' - rm -f '/usr/local/bin/pydoc3.11' - rm -f '/usr/local/bin/pydoc3.12' - rm -f '/usr/local/bin/python3' - rm -f '/usr/local/bin/python3-config' - rm -f '/usr/local/bin/python3.11' - rm -f '/usr/local/bin/python3.12' - rm -f '/usr/local/bin/python3.11-config' - rm -f '/usr/local/bin/python3.12-config' - - brew install pipx - pipx install aqtinstall - - #pip3 install -U pip - #pip3 install aqtinstall - # end of hotfix + pip3 install -U pip + pip3 install aqtinstall brew install gnupg brew install openssl@1.1 @@ -124,6 +101,12 @@ jobs: echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV echo "GIT_BRANCH=$GIT_BRANCH" >> $GITHUB_ENV + + - name: Install CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: ${{ env.CMAKE_VERSION }} + - name: Cache Qt id: cache-qt uses: pat-s/always-upload-cache@v3.0.11 diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 142f8cd76..e0a63dcc6 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -19,11 +19,12 @@ env: QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change XC_VERSION: ${{ '15.2' }} IOS_CMAKE_TOOLCHAIN_VERSION: "4.4.0" - INPUT_SDK_VERSION: arm64-ios-20240405-173 + INPUT_SDK_VERSION: arm64-ios-20240415-178 IOS_PROVISIONING_PROFILE_UUID: 59aaa8d7-516a-4592-8c58-d7d1c1f81610 KEYCHAIN: ${{ 'inputapp.keychain' }} CCACHE_DIR: /Users/runner/work/ccache - CACHE_VERSION: 3 + CMAKE_VERSION: '3.29.0' + CACHE_VERSION: 0 concurrency: group: ci-${{github.ref}}-ios @@ -74,39 +75,19 @@ jobs: - name: Install brew deps run: | - # FIXME: We should not need to do this, but there are currently some complications with Github CI. - # See https://github.com/actions/runner-images/issues/4020. - # See https://github.com/actions/runner-images/issues/8838. - - rm -f '/usr/local/bin/2to3' - rm -f '/usr/local/bin/2to3-3.11' - rm -f '/usr/local/bin/2to3-3.12' - rm -f '/usr/local/bin/idle3' - rm -f '/usr/local/bin/idle3.11' - rm -f '/usr/local/bin/idle3.12' - rm -f '/usr/local/bin/pydoc3' - rm -f '/usr/local/bin/pydoc3.11' - rm -f '/usr/local/bin/pydoc3.12' - rm -f '/usr/local/bin/python3' - rm -f '/usr/local/bin/python3-config' - rm -f '/usr/local/bin/python3.11' - rm -f '/usr/local/bin/python3.12' - rm -f '/usr/local/bin/python3.11-config' - rm -f '/usr/local/bin/python3.12-config' - - brew install pipx - pipx install aqtinstall - - #pip3 install -U pip - #pip3 install aqtinstall - - # ---- end hot fix + pip3 install -U pip + pip3 install aqtinstall brew install gnupg brew install openssl@1.1 brew install ccache brew install ninja - + + - name: Install CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: ${{ env.CMAKE_VERSION }} + - name: Install ccache run: | mkdir -p ${CCACHE_DIR} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8ef9752f8..2b68f69c2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -17,9 +17,10 @@ on: env: CCACHE_DIR: ~/.ccache - INPUT_SDK_VERSION: x64-linux-20240405-176 + INPUT_SDK_VERSION: x64-linux-20240415-181 QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change - CACHE_VERSION: 3 + CMAKE_VERSION: '3.29.0' + CACHE_VERSION: 0 concurrency: group: ci-${{github.ref}}-linux @@ -43,8 +44,13 @@ jobs: gperf autopoint '^libxcb.*-dev' libx11-xcb-dev libegl1-mesa libegl1-mesa-dev \ libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev \ autoconf-archive libgstreamer-gl1.0-0 libgstreamer-plugins-base1.0-0 libfuse2 \ - bison flex cmake ninja-build ccache lcov + bison flex ccache lcov + - name: Install CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: ${{ env.CMAKE_VERSION }} + - name: Install Qt uses: jurplel/install-qt-action@v3 with: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index acaa2702a..c9b0d5bfa 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,9 +17,10 @@ on: env: QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change - INPUT_SDK_VERSION: x64-osx-20240405-195 + INPUT_SDK_VERSION: x64-osx-20240415-201 CCACHE_DIR: /Users/runner/work/ccache - CACHE_VERSION: 1 + CACHE_VERSION: 0 + CMAKE_VERSION: '3.29.0' XC_VERSION: ${{ '15.2' }} concurrency: @@ -45,6 +46,11 @@ jobs: brew install ccache brew install ninja + - name: Install CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: ${{ env.CMAKE_VERSION }} + - name: Extract Mergin API_KEY env: MERGINSECRETS_DECRYPT_KEY: ${{ secrets.MERGINSECRETS_DECRYPT_KEY }} diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 2c185dade..51e8618fe 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -26,10 +26,11 @@ jobs: env: QT_VERSION: '6.6.3' # use scripts/update_qt_version.bash to change - INPUT_SDK_VERSION: x64-windows-20240405-223 + INPUT_SDK_VERSION: x64-windows-20240415-228 CCACHE_DIR: C:/ccache-cache # https://linux.die.net/man/1/ccache - CACHE_VERSION: 2 + CACHE_VERSION: 0 VS_VERSION: "2019" + CMAKE_VERSION: '3.29.0' QT_ARCH: "win64_msvc2019_64" steps: @@ -45,7 +46,12 @@ jobs: WORKSPACE_DIR=$(cygpath -m "${{ github.workspace }}") echo "WORKSPACE_DIR=$WORKSPACE_DIR" >> $GITHUB_OUTPUT echo "WORKSPACE_DIR: $WORKSPACE_DIR" - + + - name: Install CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: ${{ env.CMAKE_VERSION }} + - name: Install ccache shell: cmd run: | From 341bc64008bbed10806ea3d0fff16c45cabca490 Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Tue, 16 Apr 2024 16:04:43 +0200 Subject: [PATCH 11/22] fix datetime gui (#3316) fix datetime/calendar GUI --- app/mmstyle.h | 4 ++ app/qml/CMakeLists.txt | 1 + app/qml/components/MMPopup.qml | 28 ++++++++ app/qml/form/components/MMCalendarDrawer.qml | 65 ++++++++---------- .../components/calendar/MMDateTimePicker.qml | 10 +-- .../components/calendar/MMDateTumbler.qml | 68 +++++++------------ .../components/calendar/MMTimeTumbler.qml | 65 +++++++----------- .../form/components/calendar/MMTumbler.qml | 4 +- gallery/qml.qrc | 1 + 9 files changed, 118 insertions(+), 128 deletions(-) create mode 100644 app/qml/components/MMPopup.qml diff --git a/app/mmstyle.h b/app/mmstyle.h index 884e3d73a..b585bb4d8 100644 --- a/app/mmstyle.h +++ b/app/mmstyle.h @@ -272,8 +272,10 @@ class MMStyle: public QObject // Page Q_PROPERTY( double pageMargins READ number20 CONSTANT ) // distance between screen edge and components + Q_PROPERTY( double spacing5 READ number5 CONSTANT ) Q_PROPERTY( double spacing12 READ number12 CONSTANT ) // distance between page header, page content and page footer Q_PROPERTY( double spacing20 READ number20 CONSTANT ) + Q_PROPERTY( double spacing30 READ number30 CONSTANT ) Q_PROPERTY( double spacing40 READ number40 CONSTANT ) Q_PROPERTY( double maxPageWidth READ number720 CONSTANT ) // maximum page width (desktop, tablets, landscape) @@ -284,12 +286,14 @@ class MMStyle: public QObject Q_PROPERTY( double row40 READ number40 CONSTANT ) Q_PROPERTY( double row49 READ number49 CONSTANT ) Q_PROPERTY( double row50 READ number50 CONSTANT ) + Q_PROPERTY( double row54 READ number54 CONSTANT ) Q_PROPERTY( double row60 READ number60 CONSTANT ) Q_PROPERTY( double row63 READ number63 CONSTANT ) Q_PROPERTY( double row67 READ number67 CONSTANT ) Q_PROPERTY( double row80 READ number80 CONSTANT ) Q_PROPERTY( double row114 READ number114 CONSTANT ) Q_PROPERTY( double radius6 READ number6 CONSTANT ) + Q_PROPERTY( double radius8 READ number8 CONSTANT ) Q_PROPERTY( double radius12 READ number12 CONSTANT ) Q_PROPERTY( double radius16 READ number16 CONSTANT ) Q_PROPERTY( double radius20 READ number20 CONSTANT ) diff --git a/app/qml/CMakeLists.txt b/app/qml/CMakeLists.txt index 2daa4a974..425c41ed3 100644 --- a/app/qml/CMakeLists.txt +++ b/app/qml/CMakeLists.txt @@ -39,6 +39,7 @@ set(MM_QML components/MMNotificationView.qml components/MMPage.qml components/MMPageHeader.qml + components/MMPopup.qml components/MMPhoto.qml components/MMPhotoAttachment.qml components/MMPhotoPreview.qml diff --git a/app/qml/components/MMPopup.qml b/app/qml/components/MMPopup.qml new file mode 100644 index 000000000..f644d31f6 --- /dev/null +++ b/app/qml/components/MMPopup.qml @@ -0,0 +1,28 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +import QtQuick +import QtQuick.Controls + +Popup { + id: root + + modal: true + dim: false + + background: Rectangle { + color: __style.polarColor + radius: __style.radius20 + + layer.enabled: true + layer.effect: MMShadow { + radius: __style.radius20 + } + } +} diff --git a/app/qml/form/components/MMCalendarDrawer.qml b/app/qml/form/components/MMCalendarDrawer.qml index cc8739325..5e804dfae 100644 --- a/app/qml/form/components/MMCalendarDrawer.qml +++ b/app/qml/form/components/MMCalendarDrawer.qml @@ -17,7 +17,7 @@ import "./calendar" MMComponents.MMDrawer { id: root - property alias title: title.text + property string title property alias dateTime: dateTimePicker.dateToSelect property alias hasDatePicker: dateTimePicker.hasDatePicker property alias hasTimePicker: dateTimePicker.hasTimePicker @@ -25,54 +25,43 @@ MMComponents.MMDrawer { signal primaryButtonClicked - height: mainColumn.height dim: true - drawerContent: Rectangle { - id: roundedRect + drawerHeader.title: root.title - anchors.fill: parent - color: __style.polarColor + drawerContent: Item { + width: parent.width + height: scrollView.height - Column { - id: mainColumn + MMComponents.MMScrollView { + id: scrollView width: parent.width - spacing: __style.spacing20 - topPadding: __style.pageMargins - leftPadding: __style.pageMargins - rightPadding: __style.pageMargins - bottomPadding: __style.pageMargins - - Text { - id: title - - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - mainColumn.leftPadding - mainColumn.rightPadding - 2 * parent.spacing - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - font: __style.t2 - color: __style.forestColor - } - - MMDateTimePicker { - id: dateTimePicker + height: root.maxHeightHit ? root.drawerContentAvailableHeight : contentHeight + Column { width: parent.width - showSeconds: root.showSeconds - } + spacing: __style.spacing20 + + MMDateTimePicker { + id: dateTimePicker + + width: parent.width + showSeconds: root.showSeconds + } - MMComponents.MMButton { - id: primaryButton + MMComponents.MMButton { + id: primaryButton - width: parent.width - 2 * parent.spacing - visible: text.length > 0 - text: qsTr("Confirm") + width: parent.width + visible: text.length > 0 + text: qsTr("Confirm") - onClicked: { - dateTimePicker.visible = false - primaryButtonClicked() - close() + onClicked: { + dateTimePicker.visible = false + primaryButtonClicked() + close() + } } } } diff --git a/app/qml/form/components/calendar/MMDateTimePicker.qml b/app/qml/form/components/calendar/MMDateTimePicker.qml index bc874604c..5478abde3 100644 --- a/app/qml/form/components/calendar/MMDateTimePicker.qml +++ b/app/qml/form/components/calendar/MMDateTimePicker.qml @@ -39,9 +39,9 @@ Item { Column { id: mainColumn - width: root.width - 40 * __dp - spacing: 20 * __dp - topPadding: 10 * __dp + width: root.width + spacing: __style.spacing20 + topPadding: __style.margin10 // Mount and Year Row { @@ -56,7 +56,7 @@ Item { Row { id: monthYearRow - spacing: 5 * __dp + spacing: __style.spacing5 Text { text: root.locale.standaloneMonthName( monthGrid.month, Locale.LongFormat ) @@ -90,7 +90,7 @@ Item { // Right icons for changing months Row { x: parent.width - spacing - previousIcon.width - nextIcon.width - 10 * __dp - spacing: 30 * __dp + spacing: __style.spacing30 MMComponents.MMIcon { id: previousIcon diff --git a/app/qml/form/components/calendar/MMDateTumbler.qml b/app/qml/form/components/calendar/MMDateTumbler.qml index 889929447..da4b54d7f 100644 --- a/app/qml/form/components/calendar/MMDateTumbler.qml +++ b/app/qml/form/components/calendar/MMDateTumbler.qml @@ -12,10 +12,10 @@ import QtQuick.Controls import "../../../components" as MMComponents -Item { +MMComponents.MMPopup { id: root - width: row.width + 40 * __dp + width: row.width + 2 * __style.spacing20 height: row.height signal monthIndexChanged(var monthIndex) @@ -26,59 +26,41 @@ Item { readonly property int calendarYearFrom: 1900 readonly property int calendarYearTo: 2050 - Rectangle { + contentItem: Item { width: parent.width height: parent.height - anchors.horizontalCenter: parent.horizontalCenter - color: __style.polarColor - radius: 20 * __dp + Rectangle { + anchors.centerIn: parent - layer.enabled: true - layer.effect: MMComponents.MMShadow { - radius: 20 * __dp - } - - MouseArea { - anchors.fill: parent - } - } + width: parent.width - 2 * __style.margin12 + height: __style.row54 + radius: __style.radius8 - Rectangle { - anchors { - left: parent.left - leftMargin: 12 * __dp - right: parent.right - rightMargin: 12 * __dp - verticalCenter: parent.verticalCenter + color: __style.lightGreenColor } - height: 54 * __dp - radius: 8 * __dp + Row { + id: row - color: __style.lightGreenColor - } - - Row { - id: row + anchors.centerIn: parent - anchors.horizontalCenter: parent.horizontalCenter + MMTumbler { + id: monthsTumbler - MMTumbler { - id: monthsTumbler - - model: root.monthList() - currentIndex: root.initMonthIndex - width: 120 * __dp - onCurrentIndexChanged: root.monthIndexChanged(currentIndex) - } + model: root.monthList() + currentIndex: root.initMonthIndex + width: 120 * __dp + onCurrentIndexChanged: root.monthIndexChanged(currentIndex) + } - MMTumbler { - id: yearsTumble + MMTumbler { + id: yearsTumble - model: root.yearList() - currentIndex: root.initYear - root.calendarYearFrom - onCurrentItemChanged: root.yearChanged(parseInt(currentItem.text)) + model: root.yearList() + currentIndex: root.initYear - root.calendarYearFrom + onCurrentItemChanged: root.yearChanged(parseInt(currentItem.text)) + } } } diff --git a/app/qml/form/components/calendar/MMTimeTumbler.qml b/app/qml/form/components/calendar/MMTimeTumbler.qml index 4ee5e2d2a..4dffde1c8 100644 --- a/app/qml/form/components/calendar/MMTimeTumbler.qml +++ b/app/qml/form/components/calendar/MMTimeTumbler.qml @@ -12,10 +12,10 @@ import QtQuick.Controls import "../../../components" as MMComponents -Item { +MMComponents.MMPopup { id: root - width: row.width + 40 * __dp + width: row.width + 2 * __style.spacing20 height: row.height property alias hours: hoursTumbler.currentIndex @@ -23,58 +23,43 @@ Item { property alias seconds: secondsTumbler.currentIndex property bool showSeconds: false - Rectangle { + contentItem: Item { width: parent.width height: parent.height - anchors.horizontalCenter: parent.horizontalCenter - color: __style.polarColor - radius: 20 * __dp + Rectangle { + anchors.centerIn: parent - layer.enabled: true - layer.effect: MMComponents.MMShadow { - radius: 20 * __dp - } + width: parent.width - 2 * __style.margin12 + height: __style.row54 + radius: __style.radius8 - MouseArea { - anchors.fill: parent + color: __style.lightGreenColor } - } - Rectangle { - anchors { - left: parent.left - leftMargin: 12 * __dp - right: parent.right - rightMargin: 12 * __dp - verticalCenter: parent.verticalCenter - } + Row { + id: row - height: 54 * __dp - radius: 8 * __dp + anchors.centerIn: parent - color: __style.lightGreenColor - } + MMTumbler { + id: hoursTumbler - Row { - id: row + model: 24 + } - anchors.horizontalCenter: parent.horizontalCenter + MMTumbler { + id: minutesTumbler - MMTumbler { - id: hoursTumbler - model: 24 - } + model: 60 + } - MMTumbler { - id: minutesTumbler - model: 60 - } + MMTumbler { + id: secondsTumbler - MMTumbler { - id: secondsTumbler - model: 60 - visible: root.showSeconds + model: 60 + visible: root.showSeconds + } } } } diff --git a/app/qml/form/components/calendar/MMTumbler.qml b/app/qml/form/components/calendar/MMTumbler.qml index 59ba71e17..00236eeb6 100644 --- a/app/qml/form/components/calendar/MMTumbler.qml +++ b/app/qml/form/components/calendar/MMTumbler.qml @@ -11,7 +11,7 @@ import QtQuick import QtQuick.Controls Tumbler { - id: control + id: root delegate: Text { id: text @@ -20,7 +20,7 @@ Tumbler { font: Math.abs(Tumbler.displacement) < 0.4 ? __style.t1 : __style.p4 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - opacity: 1.0 - Math.abs(Tumbler.displacement) / (control.visibleItemCount / 2) + opacity: 1.0 - Math.abs(Tumbler.displacement) / (root.visibleItemCount / 2) color: Math.abs(Tumbler.displacement) < 0.4 ? __style.forestColor : __style.nightColor } } diff --git a/gallery/qml.qrc b/gallery/qml.qrc index c6def7bdd..6b3519e5b 100644 --- a/gallery/qml.qrc +++ b/gallery/qml.qrc @@ -49,6 +49,7 @@ ../app/qml/components/MMNotificationView.qml ../app/qml/components/MMDrawerDialog.qml ../app/qml/components/MMProgressBar.qml + ../app/qml/components/MMPopup.qml ../app/qml/components/MMShadow.qml ../app/qml/components/MMToolbarButton.qml ../app/qml/components/MMBadge.qml From c01cb81fd2b50172a7fcc015adcb54aa37cd656a Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Tue, 16 Apr 2024 16:29:38 +0200 Subject: [PATCH 12/22] fix disabled color --- app/mmstyle.h | 4 ++++ app/qml/dialogs/MMCloseAccountDialog.qml | 21 ++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/mmstyle.h b/app/mmstyle.h index b585bb4d8..32b1c525d 100644 --- a/app/mmstyle.h +++ b/app/mmstyle.h @@ -78,6 +78,8 @@ class MMStyle: public QObject Q_PROPERTY( QColor positiveColor READ positiveColor CONSTANT ) Q_PROPERTY( QColor warningColor READ warningColor CONSTANT ) Q_PROPERTY( QColor negativeColor READ negativeColor CONSTANT ) + Q_PROPERTY( QColor negativeLightColor READ negativeLightColor CONSTANT ) + Q_PROPERTY( QColor negativeUltraLightColor READ negativeUltraLightColor CONSTANT ) Q_PROPERTY( QColor informativeColor READ informativeColor CONSTANT ) // Colors - others @@ -356,6 +358,8 @@ class MMStyle: public QObject QColor positiveColor() {return QColor::fromString( "#C0EBCF" );} QColor warningColor() {return QColor::fromString( "#F7DDAF" );} QColor negativeColor() {return QColor::fromString( "#F0C4BC" );} + QColor negativeLightColor() {return QColor::fromString( "#FFF0ED" );} + QColor negativeUltraLightColor() {return QColor::fromString( "#FEFAF9" );} QColor informativeColor() {return QColor::fromString( "#BEDAF0" );} QColor snappingColor() {return QColor::fromString( "#BD74FF" );} diff --git a/app/qml/dialogs/MMCloseAccountDialog.qml b/app/qml/dialogs/MMCloseAccountDialog.qml index 78eaf1459..c48d72c18 100644 --- a/app/qml/dialogs/MMCloseAccountDialog.qml +++ b/app/qml/dialogs/MMCloseAccountDialog.qml @@ -60,6 +60,8 @@ MMDrawer { title: qsTr("Username") placeholderText: qsTr("Enter your username") + + errorMsg: (text.length > 2) && ( usernameInput.text !== root.username ) ? qsTr("Usernames do not match.") : "" } MMInfoBox { @@ -96,19 +98,16 @@ MMDrawer { bgndColor: __style.negativeColor fontColorHover: __style.negativeColor bgndColorHover: __style.grapeColor - fontColorDisabled: __style.grapeColor // TODO: replace with new color - bgndColorDisabled: __style.negativeColor // TODO: replace with new color + fontColorDisabled: __style.negativeColor + bgndColorDisabled: __style.negativeLightColor + + disabled: usernameInput.text !== root.username onClicked: { - if ( usernameInput.text === root.username ) { - close() - usernameInput.text = "" - usernameInput.errorMsg = "" - root.closeAccountClicked() - } - else { - usernameInput.errorMsg = qsTr("Usernames do not match.") - } + close() + usernameInput.text = "" + usernameInput.errorMsg = "" + root.closeAccountClicked() } } } From 67363aa34a76e4f311015c7ef6017e5917b44ece Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Tue, 16 Apr 2024 16:39:47 +0200 Subject: [PATCH 13/22] remove disabled property on MMButton and MMRoundButton --- app/qml/account/MMHowYouFoundUsPage.qml | 10 +++++----- app/qml/account/MMLoginPage.qml | 4 ++-- app/qml/account/MMSignUpPage.qml | 2 +- app/qml/account/MMWhichIndustryPage.qml | 8 ++++---- app/qml/components/MMButton.qml | 9 +++------ app/qml/components/MMRoundButton.qml | 4 ---- app/qml/dialogs/MMCloseAccountDialog.qml | 2 +- gallery/qml/pages/ButtonsPage.qml | 6 +++--- 8 files changed, 19 insertions(+), 26 deletions(-) diff --git a/app/qml/account/MMHowYouFoundUsPage.qml b/app/qml/account/MMHowYouFoundUsPage.qml index cf622f196..4c5583f9d 100644 --- a/app/qml/account/MMHowYouFoundUsPage.qml +++ b/app/qml/account/MMHowYouFoundUsPage.qml @@ -172,11 +172,11 @@ MMPage { text: qsTr("Continue") - disabled: { - if ( listView.currentIndex < 0 ) return true - if ( listView.model.get(listView.currentIndex).key === "social" ) return true - if ( ( listView.model.get(listView.currentIndex).key === "other" ) && root.selectedText === "" ) return true - return false + enabled: { + if ( listView.currentIndex < 0 ) return false + if ( listView.model.get(listView.currentIndex).key === "social" ) return false + if ( ( listView.model.get(listView.currentIndex).key === "other" ) && root.selectedText === "" ) return false + return true } onClicked: { diff --git a/app/qml/account/MMLoginPage.qml b/app/qml/account/MMLoginPage.qml index b86b3e45c..5929fc0ad 100644 --- a/app/qml/account/MMLoginPage.qml +++ b/app/qml/account/MMLoginPage.qml @@ -112,7 +112,7 @@ MMPage { text: qsTr( "Sign in" ) - disabled: root.pending + enabled: !root.pending onClicked: root.signInClicked( username.text, password.text ) } @@ -134,7 +134,7 @@ MMPage { type: MMButton.Types.Secondary - disabled: root.pending + enabled: !root.pending onClicked: root.signUpClicked() } diff --git a/app/qml/account/MMSignUpPage.qml b/app/qml/account/MMSignUpPage.qml index b40aac2e9..e1de1645f 100644 --- a/app/qml/account/MMSignUpPage.qml +++ b/app/qml/account/MMSignUpPage.qml @@ -122,7 +122,7 @@ MMPage { text: qsTr( "Sign up" ) - disabled: root.pending + enabled: !root.pending onClicked: { root.signUpClicked( diff --git a/app/qml/account/MMWhichIndustryPage.qml b/app/qml/account/MMWhichIndustryPage.qml index 0dd084ecb..b0c82786e 100644 --- a/app/qml/account/MMWhichIndustryPage.qml +++ b/app/qml/account/MMWhichIndustryPage.qml @@ -172,10 +172,10 @@ MMPage { text: qsTr("Continue") - disabled: { - if ( gridView.currentIndex < 0 ) return true - if ( ( gridView.model.get(gridView.currentIndex).key === "other" ) && root.selectedText === "" ) return true - return false + enabled: { + if ( gridView.currentIndex < 0 ) return false + if ( ( gridView.model.get(gridView.currentIndex).key === "other" ) && root.selectedText === "" ) return false + return true } onClicked: { diff --git a/app/qml/components/MMButton.qml b/app/qml/components/MMButton.qml index c986fb99d..631a26b3e 100644 --- a/app/qml/components/MMButton.qml +++ b/app/qml/components/MMButton.qml @@ -72,15 +72,13 @@ Button { if ( type === MMButton.Types.Tertiary ) return __style.transparentColor } - property bool disabled: false - property string iconSourceRight property string iconSourceLeft states: [ State { name: "default" - when: !root.hovered && !root.disabled + when: !root.hovered && root.enabled PropertyChanges { target: buttonContent @@ -107,7 +105,7 @@ Button { State { name: "hovered" - when: root.hovered && !root.disabled + when: root.hovered && root.enabled PropertyChanges { target: buttonContent @@ -134,7 +132,7 @@ Button { State { name: "disabled" - when: root.disabled + when: !root.enabled PropertyChanges { target: buttonContent @@ -161,7 +159,6 @@ Button { ] state: "default" - enabled: !disabled implicitHeight: root.type === MMButton.Types.Tertiary ? buttonContent.height : buttonContent.height + topPadding + bottomPadding implicitWidth: row.paintedChildrenWidth + 2 * __style.margin20 diff --git a/app/qml/components/MMRoundButton.qml b/app/qml/components/MMRoundButton.qml index 711272db2..614dede14 100644 --- a/app/qml/components/MMRoundButton.qml +++ b/app/qml/components/MMRoundButton.qml @@ -24,10 +24,6 @@ RoundButton { property color bgndColor: __style.polarColor property color bgndHoverColor: __style.mediumGreenColor - property bool disabled: false - - enabled: !disabled - contentItem: MMIcon { color: root.iconColor source: root.iconSource diff --git a/app/qml/dialogs/MMCloseAccountDialog.qml b/app/qml/dialogs/MMCloseAccountDialog.qml index c48d72c18..b4e0b4071 100644 --- a/app/qml/dialogs/MMCloseAccountDialog.qml +++ b/app/qml/dialogs/MMCloseAccountDialog.qml @@ -101,7 +101,7 @@ MMDrawer { fontColorDisabled: __style.negativeColor bgndColorDisabled: __style.negativeLightColor - disabled: usernameInput.text !== root.username + enabled: usernameInput.text === root.username onClicked: { close() diff --git a/gallery/qml/pages/ButtonsPage.qml b/gallery/qml/pages/ButtonsPage.qml index 9d449013e..97fabbb3e 100644 --- a/gallery/qml/pages/ButtonsPage.qml +++ b/gallery/qml/pages/ButtonsPage.qml @@ -64,7 +64,7 @@ ScrollView { width: parent.width / 2 - parent.spacing - disabled: true + enabled: false onClicked: text = (text === "Clicked" ? "Primary" : "Clicked") } @@ -119,7 +119,7 @@ ScrollView { width: parent.width / 2 - parent.spacing type: MMButton.Types.Secondary - disabled: true + enabled: false onClicked: text = (text === "Clicked" ? "Secondary" : "Clicked") } @@ -177,7 +177,7 @@ ScrollView { width: parent.width / 2 - parent.spacing type: MMButton.Types.Tertiary - disabled: true + enabled: false iconSourceLeft: __style.uploadIcon From 95a1b95a93e6b3f88ab159a03eddf86e864cd5ff Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Tue, 16 Apr 2024 16:42:23 +0200 Subject: [PATCH 14/22] remove binding break --- app/qml/dialogs/MMCloseAccountDialog.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/qml/dialogs/MMCloseAccountDialog.qml b/app/qml/dialogs/MMCloseAccountDialog.qml index b4e0b4071..72f5c7bae 100644 --- a/app/qml/dialogs/MMCloseAccountDialog.qml +++ b/app/qml/dialogs/MMCloseAccountDialog.qml @@ -106,7 +106,6 @@ MMDrawer { onClicked: { close() usernameInput.text = "" - usernameInput.errorMsg = "" root.closeAccountClicked() } } From f662675f79cae4bf17089aedbcd215797d5f0f8b Mon Sep 17 00:00:00 2001 From: Stefanos Natsis Date: Wed, 17 Apr 2024 08:39:53 +0300 Subject: [PATCH 15/22] Rework the project list in home tab (#3313) * add a method to make the ProjectsModel aware of the currently active project * add property in ProjectProxyModel to always sort the model's active project first * Use a single projects list in the project home tab --- app/mmstyle.h | 3 + app/projectsmodel.cpp | 13 ++ app/projectsmodel.h | 16 +- app/projectsproxymodel.cpp | 19 ++ app/projectsproxymodel.h | 8 + app/qml/project/MMProjectHomeTab.qml | 167 +++--------------- app/qml/project/MMProjectList.qml | 6 +- app/qml/project/MMProjectServerTab.qml | 4 +- .../project/components/MMProjectDelegate.qml | 5 +- app/test/testmodels.cpp | 96 ++++++++++ app/test/testmodels.h | 3 + 11 files changed, 195 insertions(+), 145 deletions(-) diff --git a/app/mmstyle.h b/app/mmstyle.h index b585bb4d8..d9e376936 100644 --- a/app/mmstyle.h +++ b/app/mmstyle.h @@ -262,12 +262,15 @@ class MMStyle: public QObject Q_PROPERTY( double margin11 READ number11 CONSTANT ) Q_PROPERTY( double margin12 READ number12 CONSTANT ) Q_PROPERTY( double margin13 READ number13 CONSTANT ) + Q_PROPERTY( double margin14 READ number14 CONSTANT ) Q_PROPERTY( double margin16 READ number16 CONSTANT ) Q_PROPERTY( double margin20 READ number20 CONSTANT ) + Q_PROPERTY( double margin28 READ number28 CONSTANT ) Q_PROPERTY( double margin30 READ number30 CONSTANT ) Q_PROPERTY( double margin32 READ number32 CONSTANT ) Q_PROPERTY( double margin36 READ number36 CONSTANT ) Q_PROPERTY( double margin40 READ number40 CONSTANT ) + Q_PROPERTY( double margin48 READ number48 CONSTANT ) Q_PROPERTY( double margin54 READ number54 CONSTANT ) // Page diff --git a/app/projectsmodel.cpp b/app/projectsmodel.cpp index fcb3d671a..71b6e62a2 100644 --- a/app/projectsmodel.cpp +++ b/app/projectsmodel.cpp @@ -123,6 +123,10 @@ QVariant ProjectsModel::data( const QModelIndex &index, int role ) const CoreUtils::log( "Project error", "Found project that is not downloaded nor remote" ); return QVariant(); } + case ProjectIsActiveProject: + { + return QVariant( project.id() == mActiveProjectId ); + } default: { if ( !project.isMergin() ) return QVariant(); @@ -160,6 +164,7 @@ QHash ProjectsModel::roleNames() const roles[Roles::ProjectSyncPending] = QStringLiteral( "ProjectSyncPending" ).toLatin1(); roles[Roles::ProjectSyncProgress] = QStringLiteral( "ProjectSyncProgress" ).toLatin1(); roles[Roles::ProjectRemoteError] = QStringLiteral( "ProjectRemoteError" ).toLatin1(); + roles[Roles::ProjectIsActiveProject] = QStringLiteral( "ProjectIsActiveProject" ).toLatin1(); return roles; } @@ -708,3 +713,11 @@ void ProjectsModel::setSyncManager( SynchronizationManager *newSyncManager ) mSyncManager = newSyncManager; emit syncManagerChanged( mSyncManager ); } + +QString ProjectsModel::activeProjectId() const { return mActiveProjectId; } + +void ProjectsModel::setActiveProjectId( const QString &projectId ) +{ + mActiveProjectId = projectId; + emit activeProjectIdChanged( mActiveProjectId ); +} diff --git a/app/projectsmodel.h b/app/projectsmodel.h index 41e178d83..3c7542686 100644 --- a/app/projectsmodel.h +++ b/app/projectsmodel.h @@ -61,7 +61,8 @@ class ProjectsModel : public QAbstractListModel ProjectStatus, ProjectSyncPending, ProjectSyncProgress, - ProjectRemoteError + ProjectRemoteError, + ProjectIsActiveProject }; Q_ENUM( Roles ) @@ -103,6 +104,9 @@ class ProjectsModel : public QAbstractListModel //! Models loading starts when listProjectsAPI is sent and finishes after endResetModel signal is emitted when projects are merged. Q_PROPERTY( bool isLoading READ isLoading NOTIFY isLoadingChanged ) + //! Use to store the active project id in the model, so that Roles::ProjectIsActiveProject can be used + Q_PROPERTY( QString activeProjectId READ activeProjectId WRITE setActiveProjectId NOTIFY activeProjectIdChanged ) + // Needed methods from QAbstractListModel Q_INVOKABLE QVariant data( const QModelIndex &index, int role ) const override; Q_INVOKABLE QModelIndex index( int row, int column = 0, const QModelIndex &parent = QModelIndex() ) const override; @@ -144,6 +148,8 @@ class ProjectsModel : public QAbstractListModel LocalProjectsManager *localProjectsManager() const; ProjectsModel::ProjectModelTypes modelType() const; + QString activeProjectId() const; + bool isLoading() const; bool hasMoreProjects() const; @@ -173,6 +179,8 @@ class ProjectsModel : public QAbstractListModel void setSyncManager( SynchronizationManager *newSyncManager ); void setLocalProjectsManager( LocalProjectsManager *localProjectsManager ); + void setActiveProjectId( const QString &projectId ); + signals: void modelInitialized(); void hasMoreProjectsChanged(); @@ -184,6 +192,8 @@ class ProjectsModel : public QAbstractListModel void syncManagerChanged( SynchronizationManager *syncManager ); void localProjectsManagerChanged( LocalProjectsManager *projectsManager ); + void activeProjectIdChanged( QString projectId ); + private: int projectIndexFromId( const QString &projectId ) const; @@ -212,6 +222,10 @@ class ProjectsModel : public QAbstractListModel MerginApi *mBackend = nullptr; // not owned LocalProjectsManager *mLocalProjectsManager = nullptr; // not owned SynchronizationManager *mSyncManager = nullptr; // not owned + + QString mActiveProjectId; + + friend class TestModels; }; #endif // PROJECTSMODEL_H diff --git a/app/projectsproxymodel.cpp b/app/projectsproxymodel.cpp index 7fe7f645a..9f30d84b5 100644 --- a/app/projectsproxymodel.cpp +++ b/app/projectsproxymodel.cpp @@ -37,6 +37,17 @@ ProjectsModel *ProjectsProxyModel::projectSourceModel() const return mModel; } +void ProjectsProxyModel::setActiveProjectAlwaysFirst( bool value ) +{ + mActiveProjectAlwaysFirst = value; + invalidate(); +} + +bool ProjectsProxyModel::activeProjectAlwaysFirst() const +{ + return mActiveProjectAlwaysFirst; +} + void ProjectsProxyModel::setSearchExpression( QString searchExpression ) { if ( mSearchExpression == searchExpression ) @@ -60,6 +71,14 @@ bool ProjectsProxyModel::lessThan( const QModelIndex &left, const QModelIndex &r { if ( mModelType == ProjectsModel::LocalProjectsModel ) { + if ( mActiveProjectAlwaysFirst ) + { + bool lProjectIsActive = mModel->data( left, ProjectsModel::ProjectIsActiveProject ).toBool(); + bool rProjectIsActive = mModel->data( right, ProjectsModel::ProjectIsActiveProject ).toBool(); + if ( lProjectIsActive || rProjectIsActive ) + return lProjectIsActive; + } + bool lProjectIsMergin = mModel->data( left, ProjectsModel::ProjectIsMergin ).toBool(); bool rProjectIsMergin = mModel->data( right, ProjectsModel::ProjectIsMergin ).toBool(); diff --git a/app/projectsproxymodel.h b/app/projectsproxymodel.h index 4a001df3f..5e7c888d5 100644 --- a/app/projectsproxymodel.h +++ b/app/projectsproxymodel.h @@ -29,12 +29,17 @@ class ProjectsProxyModel : public QSortFilterProxyModel Q_PROPERTY( QString searchExpression READ searchExpression WRITE setSearchExpression NOTIFY searchExpressionChanged ) Q_PROPERTY( ProjectsModel *projectSourceModel READ projectSourceModel WRITE setProjectSourceModel ) + //! When true, a project whose Roles::ProjectIsActiveProject is true is always sorted first + Q_PROPERTY( bool activeProjectAlwaysFirst READ activeProjectAlwaysFirst WRITE setActiveProjectAlwaysFirst ) + public: explicit ProjectsProxyModel( QObject *parent = nullptr ); ~ProjectsProxyModel() override {}; QString searchExpression() const; ProjectsModel *projectSourceModel() const; + void setActiveProjectAlwaysFirst( bool value ); + bool activeProjectAlwaysFirst() const; public slots: void setSearchExpression( QString searchExpression ); @@ -52,6 +57,9 @@ class ProjectsProxyModel : public QSortFilterProxyModel ProjectsModel *mModel = nullptr; // not owned by this, needs to be set in order to proxy model to work ProjectsModel::ProjectModelTypes mModelType = ProjectsModel::EmptyProjectsModel; QString mSearchExpression; + bool mActiveProjectAlwaysFirst = false; + + friend class TestModels; }; #endif // PROJECTSPROXYMODEL_H diff --git a/app/qml/project/MMProjectHomeTab.qml b/app/qml/project/MMProjectHomeTab.qml index c569894f9..862081bf9 100644 --- a/app/qml/project/MMProjectHomeTab.qml +++ b/app/qml/project/MMProjectHomeTab.qml @@ -32,7 +32,7 @@ Item { projectlist.refreshProjectList( searchBar.text ) } - property int spacing: 10 * __dp + property int spacing: __style.margin12 MMSearchInput { id: searchBar @@ -94,125 +94,11 @@ Item { onClicked: root.createWorkspaceRequested() } - Component { - id: activeProjectComponent - - Column { - id: currentProjectColumn - - width: ListView.view.width - - Text { - width: parent.width - height: 31 * __dp - text: qsTr("Currently open") - font: __style.p6 - color: __style.nightColor - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - - MMLine {} - - Item { width: 1; height: 20 * __dp } - - MMProjectDelegate { - id: activeProjectItem - - property var model: projectlist.projectsModel - property var index: projectlist.projectsModel.projectModelIndexFromId(root.activeProjectId) - - property string projectRemoteError: model.data(index, MM.ProjectsModel.ProjectRemoteError) ? model.data(index, MM.ProjectsModel.ProjectRemoteError) : "" - - width: parent.width - - projectIsOpened: true - projectDisplayName: model.data(index, MM.ProjectsModel.ProjectFullName) ? model.data(index, MM.ProjectsModel.ProjectFullName) : "" - projectId: model.data(index, MM.ProjectsModel.ProjectId) ? model.data(index, MM.ProjectsModel.ProjectId) : "" - projectDescription: model.data(index, MM.ProjectsModel.ProjectDescription) ? model.data(index, MM.ProjectsModel.ProjectDescription) : "" - projectIsInSync: model.data(index, MM.ProjectsModel.ProjectSyncPending) ? model.data(index, MM.ProjectsModel.ProjectSyncPending) : false - projectSyncProgress: model.data(index, MM.ProjectsModel.ProjectSyncProgress) ? model.data(index, MM.ProjectsModel.ProjectSyncProgress) : -1 - property string projectFilePath: model.data(index, MM.ProjectsModel.ProjectFilePath) ? model.data(index, MM.ProjectsModel.ProjectFilePath) : "" - - state: { - let status = model.data(index, MM.ProjectsModel.ProjectStatus) ? model.data(index, MM.ProjectsModel.ProjectStatus) : MM.ProjectStatus.NoVersion - - if ( status === MM.ProjectStatus.NeedsSync ) { - return "NeedsSync" - } - else if ( status === MM.ProjectStatus.UpToDate ) - { - return "UpToDate" - } - else if ( status === MM.ProjectStatus.NoVersion ) - { - return "NeedsSync" - } - - return "UpToDate" // fallback, should never happen - } - - projectActionButtons: { - let status = model.data(index, MM.ProjectsModel.ProjectStatus) ? model.data(index, MM.ProjectsModel.ProjectStatus) : MM.ProjectStatus.NoVersion - - if ( status === MM.ProjectStatus.NeedsSync ) { - return ["sync", "changes", "remove"] - } - else if ( status === MM.ProjectStatus.NoVersion ) { - return ["upload", "remove"] - } - return ["changes", "remove"] // UpToDate - } - - onOpenRequested: root.openProjectRequested( projectFilePath ) - - onSyncRequested: { - if ( projectRemoteError ) { - __notificationModel.addError( qsTr( "Could not synchronize project, please make sure you are logged in and have sufficient rights." ) ) - } - else if ( !model.data(index, MM.ProjectsModel.ProjectIsMergin) ) { - projectlist.projectsModel.migrateProject( projectId ) - } - else { - projectlist.projectsModel.syncProject( projectId ) - } - } - - onMigrateRequested: projectlist.projectsModel.migrateProject( projectId ) - onRemoveRequested: { - removeProjectDialog.relatedProjectId = projectId - removeProjectDialog.open() - } - onStopSyncRequested: projectlist.projectsModel.stopProjectSync( projectId ) - onShowChangesRequested: root.showLocalChangesRequested( projectId ) - } - - Item { width: 1; height: 40 * __dp } - - Text { - width: parent.width - height: 31 * __dp - text: qsTr("Downloaded projects") - font: __style.p6 - color: __style.nightColor - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - - MMLine {} - - Item { width: 1; height: 20 * __dp } - } - } - MMProjectList { id: projectlist projectModelType: MM.ProjectsModel.LocalProjectsModel activeProjectId: root.activeProjectId - hideActiveProject: true // TODO: do not hide when searching! searchText: searchBar.text spacing: root.spacing @@ -227,7 +113,34 @@ Item { return searchBar.bottom } bottom: parent.bottom - topMargin: root.spacing + topMargin: __style.margin20 + } + + activeProjectAlwaysFirst: true + projectsModel.activeProjectId: root.activeProjectId + + list.section { + property: "ProjectIsActiveProject" + criteria: ViewSection.FullString + delegate: Column { + width: ListView.view.width + spacing: __style.margin6 + + MMText { + width: parent.width + text: section === "true" ? qsTr("Currently open") : qsTr("Downloaded projects") + font: __style.p6 + color: __style.nightColor + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + MMLine {} + + Item { width: 1; height: __style.margin14 } + + } } onOpenProjectRequested: function( projectFilePath ) { @@ -236,23 +149,6 @@ Item { onShowLocalChangesRequested: function( projectId ) { root.showLocalChangesRequested( projectId ) } - - function updateListHeader() { - // ugly ugly ugly #2 - projectlist.listHeader = null - - if ( root.activeProjectId ) { - projectlist.listHeader = activeProjectComponent - } - } - - onActiveProjectIdChanged: projectlist.updateListHeader() - - Connections { - target: projectlist.projectsModel - - function onModelReset() { projectlist.updateListHeader() } - } } MMRemoveProjectDialog { @@ -263,11 +159,6 @@ Item { return } - if ( root.activeProjectId === relatedProjectId ) { - projectlist.activeProjectDeleted() // ugly, ugly, ugly - projectlist.listHeader = null - } - __inputUtils.log( "Delete project", "Project " + __localProjectsManager.projectName( relatedProjectId ) + " deleted by " + diff --git a/app/qml/project/MMProjectList.qml b/app/qml/project/MMProjectList.qml index 07954ebaf..d6a67af6c 100644 --- a/app/qml/project/MMProjectList.qml +++ b/app/qml/project/MMProjectList.qml @@ -25,11 +25,12 @@ Item { property string activeProjectId: "" property string searchText: "" property int spacing: 0 - property bool hideActiveProject: false + property bool activeProjectAlwaysFirst: false property alias projectsProxyModel: viewModel property alias projectsModel: controllerModel property alias listHeader: listview.header property alias listFooter: listview.footer + property alias list: listview signal openProjectRequested( string projectFilePath ) signal showLocalChangesRequested( string projectId ) @@ -78,6 +79,7 @@ Item { model: MM.ProjectsProxyModel { id: viewModel + activeProjectAlwaysFirst: root.activeProjectAlwaysFirst projectSourceModel: MM.ProjectsModel { id: controllerModel @@ -95,8 +97,6 @@ Item { width: ListView.view.width height: visible ? implicitHeight : 0 - visible: root.hideActiveProject ? !projectIsOpened : true - projectDisplayName: root.projectModelType === MM.ProjectsModel.WorkspaceProjectsModel ? model.ProjectName : model.ProjectFullName projectId: model.ProjectId ? model.ProjectId : "" projectDescription: model.ProjectDescription ? model.ProjectDescription : "" diff --git a/app/qml/project/MMProjectServerTab.qml b/app/qml/project/MMProjectServerTab.qml index 73a56ea21..f56097cdb 100644 --- a/app/qml/project/MMProjectServerTab.qml +++ b/app/qml/project/MMProjectServerTab.qml @@ -29,7 +29,7 @@ Item { projectlist.refreshProjectList( searchBar.text ) } - property int spacing: 10 * __dp + property int spacing: __style.margin12 MMSearchInput { id: searchBar @@ -60,7 +60,7 @@ Item { right: parent.right top: searchBar.bottom bottom: parent.bottom - topMargin: root.spacing + topMargin: __style.margin20 } onOpenProjectRequested: function( projectFilePath ) { diff --git a/app/qml/project/components/MMProjectDelegate.qml b/app/qml/project/components/MMProjectDelegate.qml index c60c811ef..3607acf7e 100644 --- a/app/qml/project/components/MMProjectDelegate.qml +++ b/app/qml/project/components/MMProjectDelegate.qml @@ -40,8 +40,11 @@ Control { topPadding: __style.margin20 rightPadding: __style.margin20 - bottomPadding: __style.margin20 leftPadding: __style.margin20 + // The last item in a section ('currently open' vs 'downloaded projects' in Home tab) gets an extra + // __style.margin28 padding, then the inset is also grown to keep the background at the correct size + bottomPadding: ListView.section !== ListView.nextSection ? __style.margin48: __style.margin20 + bottomInset: ListView.section !== ListView.nextSection ? + __style.margin28: topInset states: [ State { name: "OnServer" }, diff --git a/app/test/testmodels.cpp b/app/test/testmodels.cpp index 6140d1164..e3d41bbd7 100644 --- a/app/test/testmodels.cpp +++ b/app/test/testmodels.cpp @@ -10,6 +10,8 @@ #include "testmodels.h" #include "testutils.h" #include "featuresmodel.h" +#include "projectsmodel.h" +#include "projectsproxymodel.h" #include @@ -53,3 +55,97 @@ void TestModels::testFeaturesModel() QVariant title = fModel.data( fModel.index( 0 ), FeaturesModel::FeatureTitle ); QCOMPARE( title, QStringLiteral( "First" ) ); } + +void TestModels::testProjectsModel() +{ + Project p0; + p0.local.projectNamespace = QStringLiteral( "namespace" ); + p0.local.projectName = QStringLiteral( "project_B" ); + p0.local.projectDir = QStringLiteral( "project_B_dir" ); + + Project p1; + p1.local.projectNamespace = QStringLiteral( "namespace" ); + p1.local.projectName = QStringLiteral( "project_A" ); + p1.local.projectDir = QStringLiteral( "project_A_dir" ); + + Project p2; + p2.local.projectNamespace = QStringLiteral( "namespace" ); + p2.local.projectName = QStringLiteral( "project_C" ); + p2.local.projectDir = QStringLiteral( "project_C_dir" ); + + ProjectsModel model; + model.setModelType( ProjectsModel::LocalProjectsModel ); + model.mProjects << p0 << p1 << p2; + + QCOMPARE( model.rowCount(), 3 ); + + // test ProjectsModel::Roles::ProjectIsActiveProject + + // No active project initially + QVERIFY( !model.data( model.index( 0 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + QVERIFY( !model.data( model.index( 1 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + QVERIFY( !model.data( model.index( 2 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + + // Set an active project + model.setActiveProjectId( p0.id() ); + QCOMPARE( model.activeProjectId(), p0.id() ); + QVERIFY( model.data( model.index( 0 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + QVERIFY( !model.data( model.index( 1 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + QVERIFY( !model.data( model.index( 2 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + + // Change active project + model.setActiveProjectId( p2.id() ); + QCOMPARE( model.activeProjectId(), p2.id() ); + QVERIFY( !model.data( model.index( 0 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + QVERIFY( !model.data( model.index( 1 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); + QVERIFY( model.data( model.index( 2 ), ProjectsModel::Roles::ProjectIsActiveProject ).toBool() ); +} + +void TestModels::testProjectsProxyModel() +{ + Project p0; + p0.local.projectNamespace = QStringLiteral( "namespace" ); + p0.local.projectName = QStringLiteral( "project_B" ); + p0.local.projectDir = QStringLiteral( "project_B_dir" ); + + Project p1; + p1.local.projectNamespace = QStringLiteral( "namespace" ); + p1.local.projectName = QStringLiteral( "project_A" ); + p1.local.projectDir = QStringLiteral( "project_A_dir" ); + + Project p2; + p2.local.projectNamespace = QStringLiteral( "namespace" ); + p2.local.projectName = QStringLiteral( "project_C" ); + p2.local.projectDir = QStringLiteral( "project_C_dir" ); + + ProjectsModel model; + model.setModelType( ProjectsModel::LocalProjectsModel ); + model.mProjects << p0 << p1 << p2; + + QCOMPARE( model.rowCount(), 3 ); + + ProjectsProxyModel proxy; + proxy.setProjectSourceModel( &model ); + proxy.initialize(); + + QCOMPARE( proxy.rowCount(), 3 ); + + // No active project initially, normally sorted + QCOMPARE( proxy.data( proxy.index( 0, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p1.id() ); + QCOMPARE( proxy.data( proxy.index( 1, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p0.id() ); + QCOMPARE( proxy.data( proxy.index( 2, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p2.id() ); + + // Active project set, still normally sorted + model.setActiveProjectId( p2.id() ); + proxy.invalidate(); // re-sort + QCOMPARE( proxy.data( proxy.index( 0, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p1.id() ); + QCOMPARE( proxy.data( proxy.index( 1, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p0.id() ); + QCOMPARE( proxy.data( proxy.index( 2, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p2.id() ); + + // Active project always first set, active project is first + proxy.setActiveProjectAlwaysFirst( true ); + QVERIFY( proxy.activeProjectAlwaysFirst() ); + QCOMPARE( proxy.data( proxy.index( 0, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p2.id() ); + QCOMPARE( proxy.data( proxy.index( 1, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p1.id() ); + QCOMPARE( proxy.data( proxy.index( 2, 0 ), ProjectsModel::Roles::ProjectId ).toString(), p0.id() ); +} diff --git a/app/test/testmodels.h b/app/test/testmodels.h index 44ea8ee8e..7b27a7e3f 100644 --- a/app/test/testmodels.h +++ b/app/test/testmodels.h @@ -23,6 +23,9 @@ class TestModels : public QObject void cleanup(); // will be called after every testfunction. void testFeaturesModel(); + void testProjectsModel(); + void testProjectsProxyModel(); + }; #endif // TESTMODELS_H From 10ff669291b549ea5546d3f474c07e25eb655f3b Mon Sep 17 00:00:00 2001 From: Stefanos Natsis Date: Wed, 17 Apr 2024 08:43:00 +0300 Subject: [PATCH 16/22] remove syncInProgressAnimation leftover (#3317) --- app/qml/main.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/qml/main.qml b/app/qml/main.qml index 0d66cdd28..b9f78534f 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -814,7 +814,7 @@ ApplicationWindow { { if ( projectFullName === __activeProject.projectFullName() ) { - syncInProgressAnimation.stop() + syncButton.iconRotateAnimationRunning = false } } @@ -898,7 +898,6 @@ ApplicationWindow { { if ( projectFullName === __activeProject.projectFullName() ) { - syncInProgressAnimation.stop() missingAuthDialog.open() } } From a50969933c5d09e746d2c265488d81a5fe25c020 Mon Sep 17 00:00:00 2001 From: Stefanos Natsis Date: Wed, 17 Apr 2024 08:45:39 +0300 Subject: [PATCH 17/22] Use actual project folder in the created project notification (#3320) --- app/projectwizard.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/projectwizard.cpp b/app/projectwizard.cpp index d6c09c5da..afba22a81 100644 --- a/app/projectwizard.cpp +++ b/app/projectwizard.cpp @@ -134,7 +134,8 @@ void ProjectWizard::createProject( QString const &projectName, FieldsModel *fiel project.writePath( projectGpkgPath ); project.write( projectFilepath ); - emit notifySuccess( tr( "Project %1 created" ).arg( projectName ) ); + const QString folderName = projectDir.mid( mDataDir.size() + 1 ); + emit notifySuccess( tr( "Project %1 created" ).arg( folderName ) ); emit projectCreated( projectDir, projectName ); } From 75f1c840ddac681f76165aac09da615aa9b42c4e Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Wed, 17 Apr 2024 08:48:35 +0200 Subject: [PATCH 18/22] Fix photo popups and create workspace overlapping button (#3319) * fix codescanner button and layout * fix safe area on photo preview * fix create workspace overlapping button on android --- app/qml/account/MMCreateWorkspacePage.qml | 18 +++- app/qml/components/MMCodeScanner.qml | 104 +++++++++++++--------- app/qml/components/MMPage.qml | 4 + app/qml/components/MMPhotoPreview.qml | 82 ++++++++++------- gallery/qml/pages/MiscPage.qml | 13 +-- 5 files changed, 133 insertions(+), 88 deletions(-) diff --git a/app/qml/account/MMCreateWorkspacePage.qml b/app/qml/account/MMCreateWorkspacePage.qml index dceb66df6..0f194e5f0 100644 --- a/app/qml/account/MMCreateWorkspacePage.qml +++ b/app/qml/account/MMCreateWorkspacePage.qml @@ -21,6 +21,8 @@ MMPage { signal createWorkspaceClicked( string name ) + pageBottomMarginPolicy: MMPage.BottomMarginPolicy.PaintBehindSystemBar + pageHeader.rightItemContent: MMProgressBar { anchors.verticalCenter: parent.verticalCenter @@ -91,7 +93,9 @@ MMPage { placeholderText: qsTr( "Your Workspace" ) } - MMListSpacer { height: createButton.height + __style.margin16 } + MMListSpacer { + id: scrollBarBottomSpacer + height: createButton.height + __style.margin16 + __style.safeAreaBottom } } } @@ -99,10 +103,10 @@ MMPage { width: parent.width anchors.bottom: createButton.top - anchors.bottomMargin: __style.margin20 + anchors.bottomMargin: __style.margin40 // hide the bubble on small screens - visible: root.height - dynamicContent.height - root.pageHeader.height - 2 * height > 0 + visible: root.height + scrollBarBottomSpacer.height - dynamicContent.height - root.pageHeader.height - 2 * height > 0 title: qsTr( "Tip from Mergin Maps" ) description: qsTr( "A good candidate for a workspace name is the name of your team or organisation" ) @@ -113,7 +117,7 @@ MMPage { MMButton { id: createButton - anchors.bottom: parent.bottom + anchors.bottom: safeAreaSpacer.top anchors.bottomMargin: __style.margin8 width: parent.width @@ -122,6 +126,12 @@ MMPage { onClicked: root.createWorkspaceClicked( workspaceName.text ) } + + MMListFooterSpacer { + id: safeAreaSpacer + height: __style.safeAreaBottom + anchors.bottom: parent.bottom + } } // show error message under the respective field diff --git a/app/qml/components/MMCodeScanner.qml b/app/qml/components/MMCodeScanner.qml index 70da8e2c6..99185cfc1 100644 --- a/app/qml/components/MMCodeScanner.qml +++ b/app/qml/components/MMCodeScanner.qml @@ -13,19 +13,17 @@ import QtMultimedia import mm 1.0 as MM -Drawer { // We can keep this one as Drawer - it could actually be Popup instead +Popup { id: root - signal scanFinished( var data ) - - width: ApplicationWindow.window.width - height: ApplicationWindow.window.height + 40 * __dp - edge: Qt.BottomEdge - dim: true - interactive: false - dragMargin: 0 + parent: Overlay.overlay + visible: true + height: ApplicationWindow.window?.height ?? 0 + width: ApplicationWindow.window?.width ?? 0 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + signal scanFinished( var data ) + CaptureSession { id: captureSession @@ -37,13 +35,6 @@ Drawer { // We can keep this one as Drawer - it could actually be Popup instead videoOutput: videoOutput } - VideoOutput { - id: videoOutput - - anchors.fill: parent - fillMode: VideoOutput.PreserveAspectCrop - } - MM.QrCodeDecoder { id: qrcodeScanner @@ -55,39 +46,64 @@ Drawer { // We can keep this one as Drawer - it could actually be Popup instead } } - function unload() { - qrcodeScanner.videoSink = null - camera.active = false - captureSession.videoOutput = null - captureSession.camera = null - } + contentItem: Item { + anchors.fill: parent - Item { - id: scannerText - width: parent.width - height: (parent.width < parent.height) ? parent.height / 2 - parent.width / 4 : parent.height / 4 - anchors.horizontalCenter: parent.horizontalCenter - - MMInfoBox { - width: parent.width - 40 * __dp - title: qsTr( "Scan the QR code" ) - description: qsTr( "Please make sure that the lense is clear." ) - imageSource: __style.blueInfoImage + MMBusyIndicator { anchors.centerIn: parent + visible: true } - } - MMRoundButton { - id: closeButton + VideoOutput { + id: videoOutput - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 2 * __style.pageMargins - bgndColor: __style.lightGreenColor - iconSource: __style.closeIcon - onClicked: { - root.unload() - close() + anchors.fill: parent + fillMode: VideoOutput.PreserveAspectCrop } + + Item { + x: __style.safeAreaLeft + y: __style.safeAreaTop + width: parent.width - __style.safeAreaLeft - __style.safeAreaRight + height: parent.height - __style.safeAreaBottom - __style.safeAreaTop + + MMInfoBox { + width: Math.min(parent.width - 2 * __style.spacing20, 353 * __dp) + + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: __style.spacing20 + } + + title: qsTr( "Scan the QR code" ) + description: qsTr( "Please make sure that the lense is clear." ) + imageSource: __style.blueInfoImage + } + + MMRoundButton { + id: closeButton + + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: __style.spacing20 + } + + bgndColor: __style.lightGreenColor + iconSource: __style.closeIcon + onClicked: { + root.unload() + close() + } + } + } + } + + function unload() { + qrcodeScanner.videoSink = null + camera.active = false + captureSession.videoOutput = null + captureSession.camera = null } } diff --git a/app/qml/components/MMPage.qml b/app/qml/components/MMPage.qml index af636dec5..4328e88da 100644 --- a/app/qml/components/MMPage.qml +++ b/app/qml/components/MMPage.qml @@ -10,6 +10,10 @@ import QtQuick import QtQuick.Controls +/* + * Caller si responsible for setting up bottom safe area in pageContent! + * Top, left and right safe areas are already handled by the component + */ Page { id: root diff --git a/app/qml/components/MMPhotoPreview.qml b/app/qml/components/MMPhotoPreview.qml index bbbe6af50..347cbb2d1 100644 --- a/app/qml/components/MMPhotoPreview.qml +++ b/app/qml/components/MMPhotoPreview.qml @@ -17,58 +17,72 @@ Popup { parent: Overlay.overlay visible: true - width: ApplicationWindow.window.width - height: ApplicationWindow.window.height + height: ApplicationWindow.window?.height ?? 0 + width: ApplicationWindow.window?.width ?? 0 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside background: Rectangle { color: Qt.alpha(__style.nightAlphaColor, 0.9) } - Item { - id: photoFrame + contentItem: Item { + anchors.fill: parent - x: (root.width - imagePreview.width) / 2 - y: root.height / 4 - width: imagePreview.width - height: imagePreview.height + MMBusyIndicator { + anchors.centerIn: parent + visible: true + } - Image { - id: imagePreview + Item { + id: photoFrame - height: root.height / 2 + anchors.centerIn: parent + width: Math.min(imagePreview.width, parent.width) + height: Math.min(imagePreview.height, parent.height) - autoTransform: true - focus: true - asynchronous: true - fillMode: Image.PreserveAspectFit - } + Image { + id: imagePreview + + height: root.height / 2 - PinchHandler { - id: pinchHandler + autoTransform: true + focus: true + asynchronous: true + fillMode: Image.PreserveAspectFit + } - minimumRotation: -180 - maximumRotation: 180 - minimumScale: 0.5 - maximumScale: 10 + PinchHandler { + minimumRotation: -180 + maximumRotation: 180 + minimumScale: 0.5 + maximumScale: 10 + } + + DragHandler { } } - DragHandler { } - } + Item { + x: __style.safeAreaLeft + y: __style.safeAreaTop + width: parent.width - __style.safeAreaLeft - __style.safeAreaRight + height: parent.height - __style.safeAreaBottom - __style.safeAreaTop - MMRoundButton { - id: closeButton + MMRoundButton { + id: closeButton - anchors.top: parent.top - anchors.right: parent.right - anchors.topMargin: 2 * __style.pageMargins - anchors.rightMargin: 2 * __style.pageMargins + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: __style.spacing20 + } - bgndColor: __style.lightGreenColor - iconSource: __style.closeIcon + bgndColor: __style.lightGreenColor + iconSource: __style.closeIcon + onClicked: { + previewLoader.active = false + } + } - onClicked: { - previewLoader.active = false } } } diff --git a/gallery/qml/pages/MiscPage.qml b/gallery/qml/pages/MiscPage.qml index 1b72d3206..293c02f85 100644 --- a/gallery/qml/pages/MiscPage.qml +++ b/gallery/qml/pages/MiscPage.qml @@ -13,6 +13,7 @@ import QtQuick.Controls.Basic import "../../app/qml/components" import "../../app/qml/gps" +import "../../app/qml/gps/components" as MMGpsComponents ScrollView { id: page @@ -307,14 +308,14 @@ ScrollView { width: parent.width height: 67 * __dp - MMGpsDataText { - titleText: "Gps Data Title" - descriptionText: "Gps Data Description" + MMGpsComponents.MMGpsDataText { + title: "Gps Data Title" + value: "Gps Data Description" } - MMGpsDataText { - titleText: "Gps Data Right Title" - descriptionText: "Gps Data Right Description" + MMGpsComponents.MMGpsDataText { + title: "Gps Data Right Title" + value: "Gps Data Right Description" alignmentRight: true } } From f73c2ffbaf089a762a765ecb007a21be7b2bbecb Mon Sep 17 00:00:00 2001 From: uclaros Date: Wed, 17 Apr 2024 12:37:13 +0300 Subject: [PATCH 19/22] make sure notifications are always on top --- app/qml/components/MMNotificationView.qml | 4 ++++ app/qml/main.qml | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/qml/components/MMNotificationView.qml b/app/qml/components/MMNotificationView.qml index f7b216682..2d36054ef 100644 --- a/app/qml/components/MMNotificationView.qml +++ b/app/qml/components/MMNotificationView.qml @@ -14,6 +14,10 @@ Item { implicitHeight: ApplicationWindow.window?.height ?? 0 implicitWidth: ApplicationWindow.window?.width ?? 0 + // Make sure it is always rendered in front of everything else in the scene, including popups + parent: Overlay.overlay + z: 1 + Repeater { id: repeater diff --git a/app/qml/main.qml b/app/qml/main.qml index 0d66cdd28..e88f3b505 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -743,7 +743,6 @@ ApplicationWindow { } } - // Should be the top-most visual item MMNotificationView {} MMListDrawer { From bb4f876bbc19f4b90d419670f5cea14626eb5e2b Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Wed, 17 Apr 2024 15:23:23 +0200 Subject: [PATCH 20/22] Fix annoying error message when starting app with expired token (#3325) Fix annoying error message when starting app with expired token --- core/merginapi.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/merginapi.cpp b/core/merginapi.cpp index d7090979f..a987cd6f9 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -2896,7 +2896,24 @@ void MerginApi::getUserInfoFinished() QString message = QStringLiteral( "Network API error: %1(): %2. %3" ).arg( QStringLiteral( "getUserInfo" ), r->errorString(), serverMsg ); CoreUtils::log( "user info", QStringLiteral( "FAILED - %1" ).arg( message ) ); mUserInfo->clear(); - emit networkErrorOccurred( serverMsg, QStringLiteral( "Mergin API error: getUserInfo" ) ); + + // This is an ugly fix for #3261: if the user was logged in, but the token was already expired + // (e.g. when starting the app the next day), the flow of network requests and handlers gets + // confused because of mAuthLoopEvent involved when re-authenticating user to get new token. + // We end up requesting user info even with expired token, which of course fails with HTTP code 401 + // and user gets "Authentication information is missing or invalid." notification - this code + // prevents that. The correct solution is to get rid of the QEventLoop and to have more rigorous + // flow of network requests. + static bool firstTimeExpiredTokenAnd401 = true; + if ( firstTimeExpiredTokenAnd401 && r->attribute( QNetworkRequest::HttpStatusCodeAttribute ) == 401 && + !mUserAuth->authToken().isEmpty() && mUserAuth->tokenExpiration() < QDateTime().currentDateTimeUtc() ) + { + firstTimeExpiredTokenAnd401 = false; + } + else + { + emit networkErrorOccurred( serverMsg, QStringLiteral( "Mergin API error: getUserInfo" ) ); + } } emit userInfoReplyFinished(); From bcb196777cf8a6f785a8e4290192e01947e8a660 Mon Sep 17 00:00:00 2001 From: Stefanos Natsis Date: Wed, 17 Apr 2024 16:24:35 +0300 Subject: [PATCH 21/22] Don't emit networkErrorOccurred when cancelling operations (#3312) Manually set error message when cancelling --- core/merginapi.cpp | 21 ++++++++++++++++++++- core/merginapi.h | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/merginapi.cpp b/core/merginapi.cpp index a987cd6f9..55586e379 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -38,6 +38,7 @@ const QSet MerginApi::sIgnoreExtensions = QSet() << "gpkg-shm" const QSet MerginApi::sIgnoreImageExtensions = QSet() << "jpg" << "jpeg" << "png"; const QSet MerginApi::sIgnoreFiles = QSet() << "mergin.json" << ".DS_Store"; const int MerginApi::UPLOAD_CHUNK_SIZE = 10 * 1024 * 1024; // Should be the same as on Mergin server +const QString MerginApi::sSyncCanceledMessage = QObject::tr( "Synchronisation canceled" ); MerginApi::MerginApi( LocalProjectsManager &localProjects, QObject *parent ) @@ -430,7 +431,10 @@ void MerginApi::downloadItemReplyFinished() QString serverMsg = extractServerErrorMsg( r->readAll() ); if ( serverMsg.isEmpty() ) { - serverMsg = r->errorString(); + if ( r->error() == QNetworkReply::OperationCanceledError ) + serverMsg = sSyncCanceledMessage; + else + serverMsg = r->errorString(); } CoreUtils::log( "pull " + projectFullName, QStringLiteral( "FAILED - %1. %2" ).arg( r->errorString(), serverMsg ) ); @@ -2102,6 +2106,9 @@ void MerginApi::pushStartReplyFinished() { QByteArray data = r->readAll(); QString serverMsg = extractServerErrorMsg( data ); + if ( r->error() == QNetworkReply::OperationCanceledError ) + serverMsg = sSyncCanceledMessage; + QString code = extractServerErrorCode( data ); bool showLimitReachedDialog = EnumHelper::isEqual( code, ErrorCode::StorageLimitHit ); @@ -2189,6 +2196,9 @@ void MerginApi::pushFileReplyFinished() else { QString serverMsg = extractServerErrorMsg( r->readAll() ); + if ( r->error() == QNetworkReply::OperationCanceledError ) + serverMsg = sSyncCanceledMessage; + CoreUtils::log( "push " + projectFullName, QStringLiteral( "FAILED - %1. %2" ).arg( r->errorString(), serverMsg ) ); int httpCode = r->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); @@ -2225,6 +2235,9 @@ void MerginApi::pullInfoReplyFinished() else { QString serverMsg = extractServerErrorMsg( r->readAll() ); + if ( r->error() == QNetworkReply::OperationCanceledError ) + serverMsg = sSyncCanceledMessage; + QString message = QStringLiteral( "Network API error: %1(): %2" ).arg( QStringLiteral( "projectInfo" ), r->errorString() ); CoreUtils::log( "pull " + projectFullName, QStringLiteral( "FAILED - %1" ).arg( message ) ); @@ -2746,6 +2759,9 @@ void MerginApi::pushInfoReplyFinished() else { QString serverMsg = extractServerErrorMsg( r->readAll() ); + if ( r->error() == QNetworkReply::OperationCanceledError ) + serverMsg = sSyncCanceledMessage; + QString message = QStringLiteral( "Network API error: %1(): %2" ).arg( QStringLiteral( "projectInfo" ), r->errorString() ); CoreUtils::log( "push " + projectFullName, QStringLiteral( "FAILED - %1" ).arg( message ) ); @@ -2826,6 +2842,9 @@ void MerginApi::pushFinishReplyFinished() else { QString serverMsg = extractServerErrorMsg( r->readAll() ); + if ( r->error() == QNetworkReply::OperationCanceledError ) + serverMsg = sSyncCanceledMessage; + QString message = QStringLiteral( "Network API error: %1(): %2. %3" ).arg( QStringLiteral( "pushFinish" ), r->errorString(), serverMsg ); CoreUtils::log( "push " + projectFullName, QStringLiteral( "FAILED - %1" ).arg( message ) ); diff --git a/core/merginapi.h b/core/merginapi.h index f483fd091..0e5380bb2 100644 --- a/core/merginapi.h +++ b/core/merginapi.h @@ -374,6 +374,7 @@ class MerginApi: public QObject static const QString sMerginConfigFile; static const QString sMarketingPageRoot; static const QString sDefaultApiRoot; + static const QString sSyncCanceledMessage; static QString defaultApiRoot() { return sDefaultApiRoot; } From 53b2f34b149ed202b862889328b1a9e9cfbb0605 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Wed, 17 Apr 2024 15:25:52 +0200 Subject: [PATCH 22/22] Fix offline issues in Projects page with no/expired token (#3326) 1. incorrect "no workspace detected" message 2. endless busy wait on the home tab (unable to switch projects) 3. endless loop of requesting projects in the second tab --- app/qml/project/MMProjectController.qml | 8 ++++++-- core/merginuserauth.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/qml/project/MMProjectController.qml b/app/qml/project/MMProjectController.qml index a05e5fe86..04f52008d 100644 --- a/app/qml/project/MMProjectController.qml +++ b/app/qml/project/MMProjectController.qml @@ -303,7 +303,7 @@ Item { if ( !__merginApi.apiSupportsWorkspaces ) { return false; } - if ( !__merginApi.userAuth.hasAuthData() ) { + if ( !__merginApi.userAuth.hasValidToken() ) { return false; } // do not show the banner in case of accepting invitation or creating a workspace @@ -623,6 +623,10 @@ Item { stackView.pending = false } + function onListProjectsFailed() { + stackView.pending = false + } + function onApiVersionStatusChanged() { stackView.pending = false @@ -642,7 +646,7 @@ Item { function onAuthChanged() { stackView.pending = false - if ( __merginApi.userAuth.hasAuthData() ) { + if ( __merginApi.userAuth.hasValidToken() ) { if ( __merginApi.serverType === MM.MerginServerType.OLD || ( stackView.currentItem.objectName === "loginPage" ) ) { stackView.popPage( "loginPage" ) diff --git a/core/merginuserauth.h b/core/merginuserauth.h index fd75f4bdd..c6e4329b3 100644 --- a/core/merginuserauth.h +++ b/core/merginuserauth.h @@ -36,7 +36,7 @@ class MerginUserAuth: public QObject //! Returns true if we have a token and it is not expired, //! i.e. we should be good to do authenticated requests. - bool hasValidToken() const; + Q_INVOKABLE bool hasValidToken() const; void clear();