diff --git a/ci/Jenkinsfile.android b/ci/Jenkinsfile.android index 78ddb5d4ae2..3605a361dbc 100644 --- a/ci/Jenkinsfile.android +++ b/ci/Jenkinsfile.android @@ -63,6 +63,7 @@ pipeline { /* Control output the filename */ APP_TYPE = "${utils.getAppType()}" PLATFORM = "android/arm64" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" QT_VERSION = "6.9.2" QT_ANDROID_PATH = "/opt/qt/${env.QT_VERSION}/android_arm64_v8a" QMAKE = "/opt/qt/${env.QT_VERSION}/android_arm64_v8a/bin/qmake" diff --git a/ci/Jenkinsfile.ios b/ci/Jenkinsfile.ios index 80cf3785c2d..b4a0917a451 100644 --- a/ci/Jenkinsfile.ios +++ b/ci/Jenkinsfile.ios @@ -45,7 +45,7 @@ pipeline { GOTMPDIR = "${env.WORKSPACE_TMP}" PLATFORM = "ios/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" QT_VERSION="6.9.2" QMAKE = "/Users/admin/${QT_VERSION}/ios/bin/qmake" QT_HOST_PATH = "/Users/admin/${QT_VERSION}/macos" diff --git a/ci/Jenkinsfile.linux b/ci/Jenkinsfile.linux index 36100bfdfa0..f236df0a1ff 100644 --- a/ci/Jenkinsfile.linux +++ b/ci/Jenkinsfile.linux @@ -76,7 +76,7 @@ pipeline { environment { PLATFORM = "linux/${getArch()}${(params.USE_NWAKU ?: false) ? '-nwaku' : ''}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" /* Avoid weird bugs caused by stale cache. */ QML_DISABLE_DISK_CACHE = "true" /* Set USE_NWAKU before VERSION since version.sh uses it */ diff --git a/ci/Jenkinsfile.linux-nix b/ci/Jenkinsfile.linux-nix index 1f636061d33..06a3b383432 100644 --- a/ci/Jenkinsfile.linux-nix +++ b/ci/Jenkinsfile.linux-nix @@ -59,7 +59,7 @@ pipeline { environment { PLATFORM = "linux-nix/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" /* Avoid weird bugs caused by stale cache. */ QML_DISABLE_DISK_CACHE = "true" /* Control output the filename */ diff --git a/ci/Jenkinsfile.macos b/ci/Jenkinsfile.macos index 6dd8424d361..11b03fb61d8 100644 --- a/ci/Jenkinsfile.macos +++ b/ci/Jenkinsfile.macos @@ -70,7 +70,7 @@ pipeline { environment { PLATFORM = "macos/${getArch()}${(params.USE_NWAKU ?: false) ? '-nwaku' : ''}" /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" QT_VERSION="6.9.2" QMAKE = "/Users/admin/${QT_VERSION}/macos/bin/qmake" /* QMAKE = sh(script: "which qmake", returnStdout: true).trim() */ diff --git a/ci/Jenkinsfile.tests-ui b/ci/Jenkinsfile.tests-ui index 4c40d037b1e..013708cfef5 100644 --- a/ci/Jenkinsfile.tests-ui +++ b/ci/Jenkinsfile.tests-ui @@ -47,7 +47,7 @@ pipeline { environment { PLATFORM = 'tests/ui' /* Improve make performance */ - MAKEFLAGS = "-j4 V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" /* Makefile assumes the compiler folder is included */ QTDIR = "/opt/qt/6.9.2/gcc_64" PATH = "${env.QTDIR}/bin:${env.PATH}" @@ -70,8 +70,10 @@ pipeline { stage('Check Translations') { steps { - script { - checkTranslations() + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + script { + checkTranslations() + } } } } diff --git a/mobile/android/qt6/AndroidManifest.xml b/mobile/android/qt6/AndroidManifest.xml index c912e3bcaad..7c554f0744c 100644 --- a/mobile/android/qt6/AndroidManifest.xml +++ b/mobile/android/qt6/AndroidManifest.xml @@ -11,19 +11,21 @@ + + + + + + - - - - + + - - - - + + diff --git a/mobile/scripts/buildNimStatusClient.sh b/mobile/scripts/buildNimStatusClient.sh index 1384dca0d20..ca6aa116f9f 100755 --- a/mobile/scripts/buildNimStatusClient.sh +++ b/mobile/scripts/buildNimStatusClient.sh @@ -58,6 +58,7 @@ env $FEATURE_FLAGS ./vendor/nimbus-build-system/scripts/env.sh nim c "${PLATFORM --cpu:"$CARCH" \ --noMain:on \ -d:release \ + -d:production \ --clang.exe="$CC" \ --clang.linkerexe="$CC" \ --dynlibOverrideAll \ @@ -67,4 +68,3 @@ env $FEATURE_FLAGS ./vendor/nimbus-build-system/scripts/env.sh nim c "${PLATFORM mkdir -p "$LIB_DIR" cp "$STATUS_DESKTOP/bin/libnim_status_client$LIB_EXT" "$LIB_DIR/libnim_status_client$LIB_EXT" - diff --git a/src/app/modules/main/profile_section/advanced/module.nim b/src/app/modules/main/profile_section/advanced/module.nim index 789b4985ffd..5fa35f28c4e 100644 --- a/src/app/modules/main/profile_section/advanced/module.nim +++ b/src/app/modules/main/profile_section/advanced/module.nim @@ -63,7 +63,7 @@ method onFleetSet*(self: Module) = quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported method getLogDir*(self: Module): string = - return url_fromLocalFile(constants.LOGDIR) + return constants.LOGDIR method getWakuV2LightClientEnabled*(self: Module): bool = return self.controller.getWakuV2LightClientEnabled() diff --git a/src/app_service/service/chat/async_tasks.nim b/src/app_service/service/chat/async_tasks.nim index a3240f4bef3..099b3b23d6d 100644 --- a/src/app_service/service/chat/async_tasks.nim +++ b/src/app_service/service/chat/async_tasks.nim @@ -105,7 +105,6 @@ type AsyncSendImagesTaskArg = ref object of QObjectTaskArg chatId: string imagePathsJson: string - tempDir: string msg: string replyTo: string preferredUsername: string @@ -118,14 +117,12 @@ const asyncSendImagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = try: var images = Json.decode(arg.imagePathsJson, seq[string]) var imagePaths: seq[string] = @[] - var tempPaths: seq[string] = @[] for imagePathOrSource in images.mitems: if utils.isBase64DataUrl(imagePathOrSource): - let imagePath = save_byte_image_to_file(imagePathOrSource, arg.tempDir) + let imagePath = save_byte_image_to_file(imagePathOrSource) if imagePath != "": imagePaths.add(imagePath) - tempPaths.add(imagePath) else: imagePaths.add(imagePathOrSource) @@ -140,9 +137,6 @@ const asyncSendImagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = arg.paymentRequests ) - for imagePath in tempPaths: - removeFile(imagePath) - arg.finish(%* { "response": response, "chatId": arg.chatId, diff --git a/src/app_service/service/chat/service.nim b/src/app_service/service/chat/service.nim index 646ae7f281f..3f4c5f236e9 100644 --- a/src/app_service/service/chat/service.nim +++ b/src/app_service/service/chat/service.nim @@ -15,7 +15,6 @@ import backend/group_chat as status_group_chat import app/global/[global_singleton, utils] import app/core/eventemitter import app/core/signals/types -import constants import ../../common/message as message_common @@ -428,7 +427,6 @@ QtObject: slot: "onAsyncSendImagesDone", chatId: chatId, imagePathsJson: imagePathsJson, - tempDir: TMPDIR.replace("\\", "\\\\"), # Escape backslashes so that the JSON sent is valid (Windows issue) msg: msg, replyTo: replyTo, preferredUsername: preferredUsername, diff --git a/storybook/CMakeLists.txt b/storybook/CMakeLists.txt index 00aacd5104d..896f9800f6d 100644 --- a/storybook/CMakeLists.txt +++ b/storybook/CMakeLists.txt @@ -29,7 +29,7 @@ if(MSVC) endif() if (APPLE) - set(MACOS_VERSION_MIN_FLAGS -mmacosx-version-min=11.0) + set(MACOS_VERSION_MIN_FLAGS -mmacosx-version-min=14.0) endif() find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) diff --git a/storybook/pages/StatusNavBarTabButtonPage.qml b/storybook/pages/StatusNavBarTabButtonPage.qml index f9ef772b2d4..eda7d0b7bb4 100644 --- a/storybook/pages/StatusNavBarTabButtonPage.qml +++ b/storybook/pages/StatusNavBarTabButtonPage.qml @@ -4,6 +4,9 @@ import QtQuick.Controls import StatusQ.Controls import StatusQ.Components.private import StatusQ.Core.Theme +import StatusQ.Popups + +import shared.controls.chat.menuItems import Models import Storybook @@ -13,6 +16,7 @@ SplitView { ButtonGroup { buttons: column.children + onClicked: button => console.info("Clicked button:", button.tooltip.text) } Rectangle { @@ -82,6 +86,38 @@ SplitView { // when: ctrlNewBadgeGradient.checked // } } + StatusNavBarTabButton { + icon.name: "info" + tooltip.text: "With context menu" + thirdpartyServicesEnabled: thirdpartyServicesCtrl.checked + popupMenu: popupMenuComp + } + } + } + + Component { + id: popupMenuComp + StatusMenu { + StatusAction { + text: qsTr("Invite People") + icon.name: "share-ios" + } + + StatusAction { + text: qsTr("Community Info") + icon.name: "info" + } + + StatusAction { + text: qsTr("Community Rules") + icon.name: "text" + } + + StatusMenuSeparator {} + + MuteChatMenuItem { + title: qsTr("Mute Community") + } } } diff --git a/storybook/pages/SupportedTokenListsPanelPage.qml b/storybook/pages/SupportedTokenListsPanelPage.qml index add231f65c8..e05bff0370a 100644 --- a/storybook/pages/SupportedTokenListsPanelPage.qml +++ b/storybook/pages/SupportedTokenListsPanelPage.qml @@ -6,7 +6,6 @@ import Storybook import Models import AppLayouts.Profile.panels -import AppLayouts.Profile.stores import StatusQ @@ -39,7 +38,6 @@ SplitView { Logs { id: logs } - Pane { SplitView.fillWidth: true SplitView.fillHeight: true @@ -48,8 +46,6 @@ SplitView { anchors.fill: parent sourcesOfTokensModel: root.sourcesOfTokensModel tokensListModel: root.tokensProxyModel - - onItemClicked: logs.logEvent("SupportedTokenListsPanel::onItemClicked --> Key --> " + key) } } diff --git a/ui/StatusQ/include/StatusQ/urlutils.h b/ui/StatusQ/include/StatusQ/urlutils.h index 3fea917e6f1..0103aef852d 100644 --- a/ui/StatusQ/include/StatusQ/urlutils.h +++ b/ui/StatusQ/include/StatusQ/urlutils.h @@ -40,4 +40,8 @@ class UrlUtils : public QObject // ["jpg", "jpe", "jp", "jpeg", "png", "webp", "gif", "svg"] QStringList m_allImgExtensions; QStringList allValidImageExtensions() const { return m_allImgExtensions; } + +#ifdef Q_OS_ANDROID + QString resolveAndroidContentUrl(const QString& urlPath) const; +#endif }; diff --git a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml index 6101ed18f82..d9887b011e6 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml @@ -334,7 +334,7 @@ Control { albumCount: root.messageDetails.albumCount > 0 ? root.messageDetails.albumCount : 1 imageWidth: Math.min(messageLayout.width / root.messageDetails.albumCount - 9 * (root.messageDetails.albumCount - 1), 144) shapeType: StatusImageMessage.ShapeType.LEFT_ROUNDED - onImageClicked: root.imageClicked(image, mouse, imageSource) + onImageClicked: (image, mouse, imageSource) => root.imageClicked(image, mouse, imageSource) } } } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml index 7a2605f4876..1b89f91042c 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusNavBarTabButton.qml @@ -1,9 +1,9 @@ import QtQuick +import QtQuick.Controls import StatusQ.Core import StatusQ.Components import StatusQ.Controls -import StatusQ.Popups import StatusQ.Core.Theme StatusIconTabButton { @@ -28,7 +28,7 @@ StatusIconTabButton { visible: statusNavBarTabButton.hovered && !!statusTooltip.text delay: 50 orientation: StatusToolTip.Orientation.Right - x: statusNavBarTabButton.width + 16 + x: statusNavBarTabButton.width + Theme.padding y: statusNavBarTabButton.height / 2 - height / 2 + 4 } @@ -62,36 +62,14 @@ StatusIconTabButton { border.width: 2 } - StatusMouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: function(mouse) { - if (mouse.button === Qt.RightButton) { - if (!!popupMenuSlot.sourceComponent && !popupMenuSlot.active) - popupMenuSlot.active = true - if (popupMenuSlot.active) { - statusNavBarTabButton.highlighted = true - let btnWidth = statusNavBarTabButton.width - popupMenuSlot.item.popup(parent.x + btnWidth + 4, -2) - } - } else if (mouse.button === Qt.LeftButton) { - statusNavBarTabButton.toggle() - statusNavBarTabButton.clicked() - } - } + function openContextMenu(pos) { + if (!popupMenu) + return + const menu = popupMenu.createObject(statusNavBarTabButton) + statusTooltip.hide() + menu.popup(pos) } - Loader { - id: popupMenuSlot - sourceComponent: statusNavBarTabButton.popupMenu - active: false - onLoaded: { - popupMenuSlot.item.closeHandler = function () { - statusNavBarTabButton.highlighted = false - popupMenuSlot.active = false - } - } - } + ContextMenu.onRequested: pos => openContextMenu(pos) + onPressAndHold: openContextMenu(Qt.point(statusNavBarTabButton.pressX, statusNavBarTabButton.pressY)) } - diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml index 521a4fb9ead..095da79b4c3 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml @@ -2,10 +2,11 @@ import QtQuick import QtQuick.Controls import StatusQ.Core +import StatusQ.Core.Utils import StatusQ.Core.Theme ToolTip { - id: statusToolTip + id: root enum Orientation { Top, @@ -20,24 +21,30 @@ ToolTip { property alias arrow: arrow property color color: Theme.palette.black - implicitWidth: Math.min(maxWidth, implicitContentWidth + 16) - padding: 8 - margins: 8 - delay: 200 + implicitWidth: Math.min(maxWidth, + Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + ) + horizontalPadding: Theme.padding + verticalPadding: Theme.halfPadding + margins: Theme.halfPadding + delay: Utils.isMobile ? Application.styleHints.mousePressAndHoldInterval + : 200 + background: Item { id: statusToolTipBackground Rectangle { id: statusToolTipContentBackground - color: statusToolTip.color - radius: 8 + color: root.color + radius: Theme.radius anchors.fill: parent - anchors.bottomMargin: 8 + anchors.bottomMargin: Theme.halfPadding } Rectangle { id: arrow color: statusToolTipContentBackground.color - height: 20 - width: 20 + height: Theme.padding + width: Theme.padding rotation: 45 radius: 1 x: { @@ -45,10 +52,10 @@ ToolTip { return statusToolTipBackground.width / 2 - width / 2 + offset } if (orientation === StatusToolTip.Orientation.Left) { - return statusToolTipContentBackground.width - (width / 2) - 8 + offset + return statusToolTipContentBackground.width - statusToolTipContentBackground.radius - radius*2 + offset } if (orientation === StatusToolTip.Orientation.Right) { - return -width/2 + 8 + offset + return -width/2 + radius*2 + offset } } y: { @@ -65,14 +72,14 @@ ToolTip { } } contentItem: StatusBaseText { - text: statusToolTip.text + text: root.text color: Theme.palette.white linkColor: Theme.palette.white wrapMode: Text.Wrap font.pixelSize: Theme.additionalTextSize font.weight: Font.Medium horizontalAlignment: Text.AlignHCenter - bottomPadding: 8 + bottomPadding: Theme.halfPadding textFormat: Text.RichText elide: Text.ElideRight } diff --git a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml index fb7eb5a9515..5082dc2fa71 100644 --- a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml +++ b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusFileDialog.qml @@ -18,7 +18,9 @@ QObject { property alias currentFolder: dlg.currentFolder property string picturesShortcut: Utils.isIOS ? "assets-library://" : - StandardPaths.writableLocation(StandardPaths.PicturesLocation) + d.standardPictureLocations.length > 1 ? d.standardPictureLocations[1] // [0] is writable, don't need it here, we have StatusSaveFileDialog for that + : d.standardPictureLocations.length > 0 ? d.standardPictureLocations[0] + : "" signal accepted signal rejected @@ -33,6 +35,9 @@ QObject { QtObject { id: d + + readonly property list standardPictureLocations: StandardPaths.standardLocations(StandardPaths.PicturesLocation) + readonly property url resolvedFile: resolveFile(dlg.selectedFile) readonly property var resolvedFiles: resolveSelectedFiles(dlg.selectedFiles) @@ -45,6 +50,7 @@ QObject { resolvedLocalFile = "file:" + resolvedLocalFile return resolvedLocalFile } + function resolveSelectedFiles(selectedFiles) { if (selectedFiles.length === 0) return [] diff --git a/ui/StatusQ/src/systemutilsinternal.cpp b/ui/StatusQ/src/systemutilsinternal.cpp index 7c19c93b67e..17db141f1c1 100644 --- a/ui/StatusQ/src/systemutilsinternal.cpp +++ b/ui/StatusQ/src/systemutilsinternal.cpp @@ -1,23 +1,21 @@ -#ifdef Q_OS_ANDROID -#include -#include -#endif #include "StatusQ/systemutilsinternal.h" -#include #include #include #include #include #include #include +#include #ifdef Q_OS_ANDROID #include #include #endif +#ifdef Q_OS_IOS #include "ios_utils.h" +#endif class QuitFilter : public QObject { @@ -26,7 +24,7 @@ class QuitFilter : public QObject public: using QObject::QObject; - bool eventFilter(QObject* obj, QEvent* ev) + bool eventFilter(QObject* obj, QEvent* ev) override { if (ev->type() == QEvent::Quit) emit quit(ev->spontaneous()); @@ -60,15 +58,8 @@ void SystemUtilsInternal::restartApplication() const QMetaObject::invokeMethod(QCoreApplication::instance(), &QCoreApplication::exit, Qt::QueuedConnection, EXIT_SUCCESS); } -#if defined(Q_OS_IOS) -void save(const QByteArray& imageData) -{ - saveImageToPhotosAlbum(imageData); -} -#else void save(const QByteArray& imageData, const QString& targetDir) { - // Get current Date/Time information to use in naming of the image file const auto dateTimeString = QDateTime::currentDateTime().toString( QStringLiteral("dd-MM-yyyy_hh-mm-ss")); @@ -99,7 +90,6 @@ void save(const QByteArray& imageData, const QString& targetDir) "Downloading image failed while saving to file:" << targetFile; } -#endif void SystemUtilsInternal::downloadImageByUrl( const QUrl& url, const QString& path) const @@ -110,10 +100,15 @@ void SystemUtilsInternal::downloadImageByUrl( QNetworkReply *reply = manager.get(QNetworkRequest(url)); // accept both "file:/foo/bar" and "/foo/bar" - auto targetDir = QUrl::fromUserInput(path).toLocalFile(); + auto targetDir = QUrl::fromUserInput(path) +#ifndef Q_OS_ANDROID + .toLocalFile(); +#else + .toString(); // don't touch the "content://" URI +#endif if (targetDir.isEmpty()) - targetDir = QDir::homePath(); + targetDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); QObject::connect(reply, &QNetworkReply::finished, this, [reply, targetDir] { if(reply->error() != QNetworkReply::NoError) { @@ -125,11 +120,11 @@ void SystemUtilsInternal::downloadImageByUrl( // Extract the image data to be able to load and save it const auto btArray = reply->readAll(); Q_ASSERT(!btArray.isEmpty()); - #ifdef Q_OS_IOS - save(btArray); - #else +#ifdef Q_OS_IOS + saveImageToPhotosAlbum(btArray); +#else save(btArray, targetDir); - #endif +#endif }); } diff --git a/ui/StatusQ/src/urlutils.cpp b/ui/StatusQ/src/urlutils.cpp index 169cb0be0d4..7821b4453db 100644 --- a/ui/StatusQ/src/urlutils.cpp +++ b/ui/StatusQ/src/urlutils.cpp @@ -1,10 +1,14 @@ #include "StatusQ/urlutils.h" +#include #include #include +#include #include +#ifdef Q_OS_IOS #include "ios_utils.h" +#endif namespace { constexpr auto webpMime = "image/webp"; @@ -30,34 +34,65 @@ UrlUtils::UrlUtils(QObject *parent): QObject(parent) { bool UrlUtils::isValidImageUrl(const QUrl &url) const { - QString mimeType; - if (url.isLocalFile()) - mimeType = m_mimeDb.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent).name(); - else - mimeType = m_mimeDb.mimeTypeForUrl(url).name(); + // don't convert "content:/" like URLs to an empty path + const auto filePath = url.isLocalFile() ? url.toLocalFile() : url.toString(); + const auto mimeType = m_mimeDb.mimeTypeForFile(filePath, QMimeDatabase::MatchContent).name(); return m_validImageMimeTypes.contains(mimeType); } qint64 UrlUtils::getFileSize(const QUrl& url) { - if (url.isLocalFile()) - return QFile(url.toLocalFile()).size(); - - return 0; + // don't convert "content:/" like URLs to an empty path + const auto filePath = url.isLocalFile() ? url.toLocalFile() : url.toString(); + return QFile(filePath).size(); // will return 0 for unknown file paths } QString UrlUtils::convertUrlToLocalPath(const QString &url) const { +#ifdef Q_OS_ANDROID + return resolveAndroidContentUrl(url); +#endif + const auto localFileOrUrl = urlFromUserInput(url); // accept both "file:/foo/bar" and "/foo/bar" if (localFileOrUrl.isLocalFile()) { - #ifdef Q_OS_IOS +#ifdef Q_OS_IOS return resolveIOSPhotoAsset(localFileOrUrl.toLocalFile()); - #endif // Q_OS_IOS +#endif return localFileOrUrl.toLocalFile(); } return {}; } +#ifdef Q_OS_ANDROID +QString UrlUtils::resolveAndroidContentUrl(const QString& urlPath) const { + // test if we already have a real (resolved) path + if (urlPath.startsWith('/')) + return urlPath; + + // test if we already have a real (resolved) path in URL form + if (urlPath.startsWith(QStringLiteral("file:/"))) + return urlFromUserInput(urlPath).toLocalFile(); + + QDir dir(urlPath); + if (urlPath.endsWith('/') || dir.exists()) + return urlPath; // a directory, just return it + + QFile fileIn(urlPath); + if (!fileIn.open(QIODevice::ReadOnly)) + return urlPath; + + // save to a temp file, and return the filepath + const auto newFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + '/' + QUuid::createUuid().toString(QUuid::WithoutBraces); + QFile fileOut(newFilePath); + if (!fileOut.open(QIODevice::WriteOnly | QIODevice::Truncate)) + return urlPath; + if (fileOut.write(fileIn.readAll()) != -1) + return fileOut.fileName(); // return the real filename, not the virtual "content:/" URI + + return urlPath; +} +#endif + QStringList UrlUtils::convertUrlsToLocalPaths(const QStringList &urls) const { QStringList result; for (const auto& url: urls) { diff --git a/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml b/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml index 713ba74de71..48a5857d329 100644 --- a/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml +++ b/ui/app/AppLayouts/HomePage/delegates/HomePageGridItem.qml @@ -1,7 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Qt5Compat.GraphicalEffects +import QtQuick.Effects import StatusQ.Core import StatusQ.Controls @@ -43,9 +43,12 @@ AbstractButton { signal pinRequested - layer.enabled: true - layer.effect: Glow { - samples: 33 + RectangularShadow { + anchors.fill: background + z: background.z - 1 + offset.x: 5 + offset.y: 10 + radius: Theme.defaultPadding spread: 0.1 color: root.hovered ? Theme.palette.backdropColor : Theme.palette.dropShadow Behavior on color { ColorAnimation { duration: Theme.AnimationDuration.Fast } } diff --git a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml index d2a887bdc75..e39f633badc 100644 --- a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml @@ -257,6 +257,13 @@ Page { onClicked: onboardingFlow.popTopLevelItem() } + Keys.onPressed: function(e) { + if (e.key === Qt.Key_Back && backButton.visible) { + e.accepted = true + onboardingFlow.popTopLevelItem() + } + } + Connections { target: onboardingFlow.topLevelItem ignoreUnknownSignals: true diff --git a/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml b/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml index 5a36dd0d142..dd93d8c1865 100644 --- a/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml +++ b/ui/app/AppLayouts/Onboarding/controls/LoginUserSelector.qml @@ -90,7 +90,7 @@ Control { id: dropdown objectName: "dropdown" - closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnEscape + bottomSheetAllowed: false directParent: root relativeY: root.height + 2 diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 7cc7d47f765..cce271b725c 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -150,7 +150,7 @@ StatusSectionLayout { id: settingsEntriesModel showWalletEntries: root.walletStore.isWalletEnabled - showBrowserEntries: root.isBrowserEnabled + showBrowserEntries: root.isBrowserEnabled && localAccountSensitiveSettings.isBrowserEnabled showBackUpSeed: !root.privacyStore.mnemonicBackedUp backUpSeedBadgeCount: root.profileStore.userDeclinedBackupBanner ? 0 : showBackUpSeed isKeycardEnabled: root.isKeycardEnabled @@ -455,6 +455,7 @@ StatusSectionLayout { advancedStore: root.advancedStore walletStore: root.walletStore isFleetSelectionEnabled: fleetSelectionEnabled + isBrowserEnabled: root.isBrowserEnabled sectionTitle: settingsEntriesModel.getNameForSubsection(Constants.settingsSubsection.advanced) contentWidth: d.contentWidth } diff --git a/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml b/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml index fe2971b9b19..6fb951dfd5b 100644 --- a/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml +++ b/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml @@ -217,7 +217,7 @@ SortFilterProxyModel { case Constants.settingsSubsection.ensUsernames: case Constants.settingsSubsection.wallet: return root.showWalletEntries - case Constants.settingsSubsection.browser: + case Constants.settingsSubsection.browserSettings: return root.showBrowserEntries case Constants.settingsSubsection.backUpSeed: return root.showBackUpSeed diff --git a/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml b/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml index 83fa36f10fc..d422bac371b 100644 --- a/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/SupportedTokenListsPanel.qml @@ -6,7 +6,6 @@ import StatusQ.Core import StatusQ.Components import StatusQ.Controls import StatusQ.Core.Theme -import StatusQ.Core.Utils as SQUtils import SortFilterProxyModel import shared.controls @@ -20,11 +19,10 @@ StatusListView { required property var sourcesOfTokensModel // Expected roles: key, name, updatedAt, source, version, tokensCount, image required property var tokensListModel // Expected roles: name, symbol, image, chainName, explorerUrl - signal itemClicked(string key) - implicitHeight: contentHeight model: root.sourcesOfTokensModel spacing: Theme.halfPadding + delegate: StatusListItem { height: ProfileUtils.defaultDelegateHeight width: ListView.view.width @@ -65,36 +63,39 @@ StatusListView { required property string name required property string image required property string source - required property int updatedAt + required property double updatedAt required property string version required property int tokensCount - - Component.onCompleted: popup.open() + } + onObjectAdded: function(index, delegate) { + popupComp.createObject(root, { + title: delegate.name, + sourceImage: delegate.image, + sourceUrl: delegate.source, + sourceVersion: delegate.version, + updatedAt: delegate.updatedAt, + tokensCount: delegate.tokensCount + }).open() } } - TokenListPopup { - id: popup + Component { + id: popupComp + TokenListPopup { + destroyOnClose: true - sourceImage: delegate.image - sourceUrl: delegate.source - sourceVersion: delegate.version - updatedAt: delegate.updatedAt - tokensCount: delegate.tokensCount + tokensListModel: SortFilterProxyModel { + sourceModel: root.tokensListModel - title: delegate.name - - tokensListModel: SortFilterProxyModel { - sourceModel: root.tokensListModel - - // Filter by source - filters: RegExpFilter { - roleName: "sources" - pattern: "\;" + keyFilter.value + "\;" + // Filter by source + filters: RegExpFilter { + roleName: "sources" + pattern: "\;" + keyFilter.value + "\;" + } } - } - onLinkClicked: (link) => Global.openLink(link) - onClosed: keyFilter.value = "" + onLinkClicked: (link) => Global.openLink(link) + onClosed: keyFilter.value = "" + } } } diff --git a/ui/app/AppLayouts/Profile/views/AdvancedView.qml b/ui/app/AppLayouts/Profile/views/AdvancedView.qml index 8515535ce49..3c593a721d5 100644 --- a/ui/app/AppLayouts/Profile/views/AdvancedView.qml +++ b/ui/app/AppLayouts/Profile/views/AdvancedView.qml @@ -11,6 +11,7 @@ import shared.popups import shared.status import shared.controls +import StatusQ import StatusQ.Components import StatusQ.Controls import StatusQ.Controls.Validators @@ -36,6 +37,7 @@ SettingsContentBase { property WalletStore walletStore property bool isFleetSelectionEnabled + property bool isBrowserEnabled: true Item { id: advancedContainer @@ -54,6 +56,7 @@ SettingsContentBase { width: root.contentWidth StatusSettingsLineButton { + width: parent.width text: qsTr("Fleet") currentValue: root.advancedStore.fleet onClicked: fleetModal.open() @@ -62,12 +65,14 @@ SettingsContentBase { StatusSettingsLineButton { id: labelScrolling + width: parent.width text: qsTr("Chat scrolling") currentValue: root.advancedStore.isCustomScrollingEnabled ? qsTr("Custom") : qsTr("System") onClicked: scrollingModal.open() } StatusSettingsLineButton { + width: parent.width text: qsTr("Minimize on close") isSwitch: true checked: !localAccountSensitiveSettings.quitOnClose @@ -75,6 +80,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Mainnet data verified by Nimbus") isSwitch: true checked: root.advancedStore.isNimbusProxyEnabled @@ -88,22 +94,28 @@ SettingsContentBase { anchors.right: parent.right anchors.leftMargin: Theme.padding anchors.rightMargin: Theme.padding - text: qsTr("Application Logs") + text: qsTr("Application Logs") + " (" + root.advancedStore.logDir() + ")" font.underline: mouseArea.containsMouse color: Theme.palette.primaryColor1 topPadding: 23 + wrapMode: Text.Wrap + elide: Text.ElideRight StatusMouseArea { id: mouseArea anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true - onClicked: { - Qt.openUrlExternally(root.advancedStore.logDir()) - } + onClicked: logsFolderDialog.open() } } + StatusFolderDialog { + id: logsFolderDialog + title: qsTr("Application Logs") + currentFolder: root.advancedStore.logDir() + } + Item { id: spacer1 height: Theme.bigPadding @@ -125,9 +137,11 @@ SettingsContentBase { } StatusSettingsLineButton { + visible: root.isBrowserEnabled // feature flag + width: parent.width text: qsTr("Web/dApp Browser") isSwitch: true - checked: localAccountSensitiveSettings.isBrowserEnabled + checked: localAccountSensitiveSettings.isBrowserEnabled // user setting onToggled: { if (checked) { confirmationPopup.experimentalFeature = root.advancedStore.experimentalFeatures.browser @@ -139,6 +153,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Node Management") isSwitch: true checked: localAccountSensitiveSettings.nodeManagementEnabled @@ -153,6 +168,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Archive Protocol Enabled") visible: !SQUtils.Utils.isMobile isSwitch: true @@ -163,6 +179,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("ENS Community Permissions Enabled") isSwitch: true checked: root.advancedStore.ensCommunityPermissionsEnabled @@ -186,6 +203,8 @@ SettingsContentBase { } StatusSettingsLineButton { + visible: !SQUtils.Utils.isMobile + width: parent.width text: qsTr("Enable creation of sharded communities") isSwitch: true checked: root.advancedStore.isWakuV2ShardedCommunitiesEnabled @@ -287,6 +306,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Full developer mode") enabled: { return !localAccountSensitiveSettings.downloadChannelMessagesEnabled || @@ -303,6 +323,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Enable translations") isSwitch: true checked: localAppSettings.translationsEnabled @@ -325,6 +346,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Download messages") isSwitch: true checked: localAccountSensitiveSettings.downloadChannelMessagesEnabled @@ -334,6 +356,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Debug") isSwitch: true enabled: !root.advancedStore.isRuntimeLogLevelSet @@ -359,6 +382,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Auto message") isSwitch: true checked: root.advancedStore.isAutoMessageEnabled @@ -368,6 +392,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width objectName: "manageCommunitiesOnTestnetButton" text: qsTr("Manage communities on testnet") isSwitch: true @@ -378,6 +403,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("Enable community tokens refreshing") isSwitch: true checked: root.advancedStore.refreshTokenEnabled @@ -387,6 +413,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width text: qsTr("How many log files to keep archived") currentValue: root.advancedStore.logMaxBackups.toString() onClicked: { @@ -395,6 +422,7 @@ SettingsContentBase { } StatusSettingsLineButton { + width: parent.width id: rpcStatsButton text: qsTr("RPC statistics") onClicked: rpcStatsModal.open() diff --git a/ui/app/AppLayouts/Profile/views/BrowserView.qml b/ui/app/AppLayouts/Profile/views/BrowserView.qml index 50f3e1a4a2a..df1515630e2 100644 --- a/ui/app/AppLayouts/Profile/views/BrowserView.qml +++ b/ui/app/AppLayouts/Profile/views/BrowserView.qml @@ -45,10 +45,8 @@ SettingsContentBase { anchors.rightMargin: Theme.padding } - // TODO: Replace with StatusQ StatusListItem component StatusSettingsLineButton { - anchors.leftMargin: 0 - anchors.rightMargin: 0 + width: parent.width text: qsTr("Search engine used in the address bar") currentValue: { switch (accountSettings.shouldShowBrowserSearchEngine) { diff --git a/ui/app/AppLayouts/Profile/views/wallet/MainView.qml b/ui/app/AppLayouts/Profile/views/wallet/MainView.qml index d1c5aec1be3..fcc9b42cfeb 100644 --- a/ui/app/AppLayouts/Profile/views/wallet/MainView.qml +++ b/ui/app/AppLayouts/Profile/views/wallet/MainView.qml @@ -150,7 +150,7 @@ Column { id: accountOrderBetaTag anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: accountOrderItem.statusListItemTitle.width + Theme.bigPadding + anchors.leftMargin: accountOrderItem.statusListItemTitle.width + parent.leftPadding + Theme.bigPadding tooltipText: qsTr("Under construction, you might experience some minor issues") cursorShape: Qt.PointingHandCursor } @@ -177,7 +177,7 @@ Column { id: manageTokensBetaTag anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: manageTokensItem.statusListItemTitle.width + Theme.bigPadding + anchors.leftMargin: manageTokensItem.statusListItemTitle.width + parent.leftPadding + Theme.bigPadding tooltipText: qsTr("Under construction, you might experience some minor issues") cursorShape: Qt.PointingHandCursor } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 0d9561d973f..1a2d31d67df 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -827,7 +827,7 @@ Item { id: d readonly property int activeSectionType: appMain.rootStore.activeSectionType - readonly property bool isBrowserEnabled: featureFlagsStore.browserEnabled && localAccountSensitiveSettings.isBrowserEnabled + readonly property bool isBrowserEnabled: appMain.featureFlagsStore.browserEnabled && localAccountSensitiveSettings.isBrowserEnabled function openHomePage() { appMain.rootStore.setActiveSectionBySectionType(Constants.appSection.homePage) @@ -1239,6 +1239,11 @@ Item { value: Constants.appSection.browser enabled: d.isBrowserEnabled } + ValueFilter { + roleName: "sectionType" + value: Constants.appSection.node + enabled: localAccountSensitiveSettings.nodeManagementEnabled + } }, ValueFilter { roleName: "enabled" diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index a2f4a27dfcd..e206cd82cc8 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -1,3 +1,4 @@ +import QtCore import QtQuick import QtQuick.Layouts import QtQuick.Window @@ -933,6 +934,7 @@ QtObject { title: qsTr("Please choose a directory") modality: Qt.NonModal + currentFolder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) onAccepted: { SystemUtils.downloadImageByUrl(imageSource, selectedFolder) diff --git a/ui/i18n/CMakeLists.txt b/ui/i18n/CMakeLists.txt index fd8a8dedb0f..dcc625d67ab 100644 --- a/ui/i18n/CMakeLists.txt +++ b/ui/i18n/CMakeLists.txt @@ -13,6 +13,7 @@ file(GLOB_RECURSE COLLECTED_SOURCE_FILES ${CMAKE_SOURCE_DIR}/../*.qml ) +list(FILTER COLLECTED_SOURCE_FILES EXCLUDE REGEX "${CMAKE_SOURCE_DIR}/../StatusQ/build/.*" ) qt6_add_lupdate( LUPDATE_TARGET update_application_translations # name of the cmake target diff --git a/ui/i18n/qml_base_en.ts b/ui/i18n/qml_base_en.ts index 7662ee17ec9..73dabe34f1c 100644 --- a/ui/i18n/qml_base_en.ts +++ b/ui/i18n/qml_base_en.ts @@ -7691,6 +7691,13 @@ Please add it and try again. + + FeeRow + + Max. + + + FeesBox @@ -8439,7 +8446,7 @@ L2 fee: %2 - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 @@ -8974,6 +8981,10 @@ Are you sure you want to do this? PIN correct + + Keycard blocked + + %n attempt(s) remaining @@ -15904,6 +15915,10 @@ to load Format not supported. + + Format not supported. File: %1 + + Upload %1 only diff --git a/ui/i18n/qml_base_lokalise_en.ts b/ui/i18n/qml_base_lokalise_en.ts index 4a89e0ecd27..10838ed3ecb 100644 --- a/ui/i18n/qml_base_lokalise_en.ts +++ b/ui/i18n/qml_base_lokalise_en.ts @@ -9394,6 +9394,14 @@ Remove + + FeeRow + + Max. + FeeRow + Max. + + FeesBox @@ -10303,9 +10311,9 @@ Image format not supported - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 ImageCropWorkflow - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 Supported image extensions: %1 @@ -10944,6 +10952,11 @@ KeycardEnterPinPage PIN correct + + Keycard blocked + KeycardEnterPinPage + Keycard blocked + %n attempt(s) remaining KeycardEnterPinPage @@ -19360,6 +19373,11 @@ StatusChatImageExtensionValidator Format not supported. + + Format not supported. File: %1 + StatusChatImageExtensionValidator + Format not supported. File: %1 + Upload %1 only StatusChatImageExtensionValidator diff --git a/ui/i18n/qml_cs.ts b/ui/i18n/qml_cs.ts index 82a790d59b0..8429c2b50aa 100644 --- a/ui/i18n/qml_cs.ts +++ b/ui/i18n/qml_cs.ts @@ -7718,6 +7718,13 @@ Please add it and try again. Odstranit + + FeeRow + + Max. + + + FeesBox @@ -8467,7 +8474,7 @@ L2 poplatek: %2 - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 @@ -9010,6 +9017,10 @@ Are you sure you want to do this? PIN correct + + Keycard blocked + + %n attempt(s) remaining @@ -15972,6 +15983,10 @@ to load Format not supported. + + Format not supported. File: %1 + + Upload %1 only diff --git a/ui/i18n/qml_ko.ts b/ui/i18n/qml_ko.ts index 589856af3ab..5946a99d50e 100644 --- a/ui/i18n/qml_ko.ts +++ b/ui/i18n/qml_ko.ts @@ -1205,7 +1205,7 @@ Application Logs - 애플리케이션 로그 + Experimental features @@ -7664,6 +7664,13 @@ Please add it and try again. 제거 + + FeeRow + + Max. + + + FeesBox @@ -8412,8 +8419,8 @@ L2 fee: %2 지원하지 않는 이미지 형식입니다 - Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. - 선택한 이미지의 형식은 지원되지 않습니다. 아마도 잘못된 파일, 손상된 파일이거나 파일 확장자가 올바르지 않은 경우일 수 있습니다. + Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1 + Supported image extensions: %1 @@ -8939,6 +8946,10 @@ Are you sure you want to do this? PIN correct PIN이 올바릅니다 + + Keycard blocked + Keycard가 차단됨 + %n attempt(s) remaining @@ -15840,6 +15851,10 @@ to load Format not supported. 지원되지 않는 형식입니다. + + Format not supported. File: %1 + + Upload %1 only %1만 업로드 diff --git a/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml b/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml index 3cc6c7304b4..1d857aba76d 100644 --- a/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml +++ b/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml @@ -4,8 +4,6 @@ import QtQuick.Controls import utils import StatusQ.Popups -import shared.controls.chat.menuItems - StatusMenu { property bool isCommunityChat: false diff --git a/ui/imports/shared/popups/ImageCropWorkflow.qml b/ui/imports/shared/popups/ImageCropWorkflow.qml index 17f4f28625e..56ff5fce9ff 100644 --- a/ui/imports/shared/popups/ImageCropWorkflow.qml +++ b/ui/imports/shared/popups/ImageCropWorkflow.qml @@ -44,24 +44,29 @@ Item { nameFilters: [qsTr("Supported image formats (%1)").arg(UrlUtils.validImageNameFilters)] onAccepted: { if (fileDialog.selectedFiles.length > 0) { - const url = fileDialog.selectedFiles[0] + const url = fileDialog.selectedFile if (Utils.isValidDragNDropImage(url)) cropImage(url) - else + else { + errorDialog.fileOpened = url errorDialog.open() + } } } } // FileDialog StatusDialog { id: errorDialog + + property string fileOpened + title: qsTr("Image format not supported") width: 480 contentItem: ColumnLayout { StatusBaseText { Layout.fillWidth: true wrapMode: Text.WordWrap - text: qsTr("Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension.") + text: qsTr("Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension. The requested file was: %1").arg(errorDialog.fileOpened) } StatusBaseText { Layout.fillWidth: true diff --git a/ui/imports/shared/status/StatusChatImageExtensionValidator.qml b/ui/imports/shared/status/StatusChatImageExtensionValidator.qml index 9a6bf558741..676fefe3f49 100644 --- a/ui/imports/shared/status/StatusChatImageExtensionValidator.qml +++ b/ui/imports/shared/status/StatusChatImageExtensionValidator.qml @@ -7,13 +7,19 @@ import utils StatusChatImageValidator { id: root - errorMessage: qsTr("Format not supported.") + errorMessage: !!lastFailedImgPath ? qsTr("Format not supported. File: %1").arg(lastFailedImgPath) : qsTr("Format not supported.") secondaryErrorMessage: qsTr("Upload %1 only").arg(UrlUtils.validPreferredImageExtensions.map(ext => ext.toUpperCase() + "s").join(", ")) + property string lastFailedImgPath + onImagesChanged: { let isValid = true root.validImages = images.filter(img => { const isImage = Utils.isValidDragNDropImage(img) + if (!isImage) + root.lastFailedImgPath = img + else + root.lastFailedImgPath = "" isValid = isValid && isImage return isImage }) diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml index d98c18ee981..f97ad161231 100644 --- a/ui/imports/shared/status/StatusChatInput.qml +++ b/ui/imports/shared/status/StatusChatInput.qml @@ -21,6 +21,8 @@ import StatusQ.Core.Utils as StatusQUtils import StatusQ.Components import StatusQ.Controls as StatusQ +import QtModelsToolkit + Rectangle { id: control objectName: "statusChatInput" @@ -1440,7 +1442,8 @@ Rectangle { implicitWidth: 32 icon.name: "send" type: StatusQ.StatusFlatRoundButton.Type.Tertiary - visible: messageInputField.length > 0 || control.fileUrlsAndSources.length > 0 + visible: messageInputField.length > 0 || control.fileUrlsAndSources.length > 0 || + (!!control.paymentRequestModel && control.paymentRequestModel.ModelCount.count > 0) onClicked: { control.onKeyPress({modifiers: d.kbdModifierToSendMessage, key: Qt.Key_Return}) } diff --git a/ui/imports/shared/status/StatusSettingsLineButton.qml b/ui/imports/shared/status/StatusSettingsLineButton.qml index 3994ff395a0..ace26133aef 100644 --- a/ui/imports/shared/status/StatusSettingsLineButton.qml +++ b/ui/imports/shared/status/StatusSettingsLineButton.qml @@ -15,6 +15,8 @@ ItemDelegate { property int badgeValue property int badgeRadius: 9 + implicitHeight: 64 + horizontalPadding: Theme.padding spacing: Theme.padding diff --git a/ui/main.qml b/ui/main.qml index 21c868ceaa0..5b89401d3d2 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -441,6 +441,7 @@ StatusWindow { anchors.topMargin: Qt.platform.os === SQUtils.Utils.mac ? 0 : parent.SafeArea.margins.top anchors.leftMargin: parent.SafeArea.margins.left anchors.rightMargin: parent.SafeArea.margins.right + anchors.bottomMargin: parent.SafeArea.margins.bottom sourceComponent: onboardingV2 } @@ -479,8 +480,6 @@ StatusWindow { id: onboardingLayout objectName: "startupOnboardingLayout" - bottomPadding: applicationWindow.SafeArea.margins.bottom - isKeycardEnabled: featureFlagsStore.keycardEnabled networkChecksEnabled: true diff --git a/vendor/DOtherSide b/vendor/DOtherSide index dcbe0cc7584..51f6e4c3411 160000 --- a/vendor/DOtherSide +++ b/vendor/DOtherSide @@ -1 +1 @@ -Subproject commit dcbe0cc75846b183b45bfbf13f1c6c54325d4306 +Subproject commit 51f6e4c34112e27fbebfb1a77557cd4fd93f2c48 diff --git a/vendor/nimqml b/vendor/nimqml index 3567d72d3f1..629898020d5 160000 --- a/vendor/nimqml +++ b/vendor/nimqml @@ -1 +1 @@ -Subproject commit 3567d72d3f13bd1c333fbdcacea4e8ca00016f02 +Subproject commit 629898020d590ba01b223db15623b6dd90f4c8ef